Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…

)\n\n\ndef _looks_like_inline_yaml(value):\n \"\"\"Return True if the input resembles inline YAML content.\"\"\"\n stripped = value.strip()\n if not stripped:\n return False\n if '\\n' in value:\n return True\n if stripped.startswith(('---', '{', '[')):\n return True\n return bool(_INLINE_YAML_KEY_PATTERN.match(stripped))\n\n\ndef resolve_k8s_yaml(k8s_yaml_value):\n \"\"\"Resolve --k8s-yaml as either inline YAML or a path to an existing file.\"\"\"\n if not k8s_yaml_value:\n return ''\n\n candidate_path = Path(k8s_yaml_value).expanduser()\n if candidate_path.is_file():\n return candidate_path.read_text(encoding='utf-8')\n\n if _looks_like_inline_yaml(k8s_yaml_value):\n return k8s_yaml_value\n\n raise ValueError(\n f\"--k8s-yaml must be inline YAML content or an existing file path: {k8s_yaml_value}\"\n )\n\n\nclass DeclarativePipelineGenerator:\n \"\"\"Generator for Declarative Jenkins Pipelines\"\"\"\n\n def __init__(self, config):\n self.config = config\n self.pipeline_parts = []\n\n def generate(self):\n \"\"\"Generate complete declarative pipeline\"\"\"\n # Start pipeline block\n self.pipeline_parts.append(\"pipeline {\")\n\n # Add agent\n self._add_agent()\n\n # Add environment (if specified)\n self._add_environment()\n\n # Add parameters (if specified)\n self._add_parameters()\n\n # Add options (if specified)\n self._add_options()\n\n # Add triggers (if specified)\n self._add_triggers()\n\n # Add tools (if specified)\n self._add_tools()\n\n # Add stages\n self._add_stages()\n\n # Add post conditions\n self._add_post()\n\n # Close pipeline block\n self.pipeline_parts.append(\"}\")\n\n # Format and return\n content = '\\n'.join(self.pipeline_parts)\n return FormattingHelpers.format_jenkinsfile(\n FormattingHelpers.add_header_comment(\n content,\n f\"Declarative Pipeline - {self.config.get('name', 'Generated Pipeline')}\"\n )\n )\n\n def _add_agent(self):\n \"\"\"Add agent configuration\"\"\"\n agent_type = self.config.get('agent', 'any')\n\n if agent_type == 'docker':\n agent_block = DeclarativeSyntax.agent_block(\n 'docker',\n image=self.config.get('docker_image', 'ubuntu:latest'),\n args=self.config.get('docker_args', ''),\n reuseNode=self.config.get('docker_reuse_node', False)\n )\n elif agent_type == 'dockerfile':\n agent_block = DeclarativeSyntax.agent_block(\n 'dockerfile',\n filename=self.config.get('dockerfile', 'Dockerfile'),\n dir=self.config.get('dockerfile_dir', '.'),\n additionalBuildArgs=self.config.get('dockerfile_build_args', '')\n )\n elif agent_type == 'kubernetes':\n agent_block = DeclarativeSyntax.agent_block(\n 'kubernetes',\n yaml=self.config.get('k8s_yaml', ''),\n inheritFrom=self.config.get('k8s_inherit_from', '')\n )\n elif agent_type == 'label':\n agent_block = DeclarativeSyntax.agent_block(\n 'label',\n label=self.config.get('agent_label', 'linux')\n )\n elif agent_type == 'none':\n agent_block = DeclarativeSyntax.agent_block('none')\n else:\n agent_block = DeclarativeSyntax.agent_block('any')\n\n self.pipeline_parts.append(agent_block)\n\n def _add_environment(self):\n \"\"\"Add environment variables\"\"\"\n env_vars = self.config.get('environment', {})\n credentials = self.config.get('credentials', {})\n\n if env_vars or credentials:\n env_block = DeclarativeSyntax.environment_block(env_vars, credentials)\n if env_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(env_block)\n\n def _add_parameters(self):\n \"\"\"Add parameters\"\"\"\n parameters = self.config.get('parameters', [])\n\n if parameters:\n param_block = DeclarativeSyntax.parameters_block(parameters)\n if param_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(param_block)\n\n def _add_options(self):\n \"\"\"Add options\"\"\"\n options = self.config.get('options', {})\n\n if options:\n options_block = DeclarativeSyntax.options_block(options)\n if options_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(options_block)\n\n def _add_triggers(self):\n \"\"\"Add triggers\"\"\"\n triggers = self.config.get('triggers', {})\n\n if triggers:\n triggers_block = DeclarativeSyntax.triggers_block(triggers)\n if triggers_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(triggers_block)\n\n def _add_tools(self):\n \"\"\"Add tools\"\"\"\n tools = self.config.get('tools', {})\n\n if tools:\n tools_block = DeclarativeSyntax.tools_block(tools)\n if tools_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(tools_block)\n\n def _add_stages(self):\n \"\"\"Add stages based on configuration\"\"\"\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(\" stages {\")\n\n # Get stage list from config or use default\n stages = self.config.get('stages', ['build', 'test'])\n\n # Get build tool pattern if specified\n build_tool = self.config.get('build_tool', 'maven')\n pattern = PipelinePatterns.ci_pattern(build_tool)\n\n # Generate stages based on type\n for stage in stages:\n if stage == 'checkout':\n self.pipeline_parts.append(StageTemplates.checkout_stage(\n scm_url=self.config.get('scm_url'),\n branch=self.config.get('branch', 'main'),\n credentials=self.config.get('scm_credentials')\n ))\n elif stage == 'build':\n build_cmd = self.config.get('build_cmd', pattern['build_cmd'])\n self.pipeline_parts.append(StageTemplates.build_stage(build_cmd))\n elif stage == 'test':\n test_cmd = self.config.get('test_cmd', pattern['test_cmd'])\n test_results = self.config.get('test_results', pattern['test_results'])\n self.pipeline_parts.append(StageTemplates.test_stage(test_cmd, test_results))\n elif stage == 'deploy':\n deploy_cmd = self.config.get('deploy_cmd', './deploy.sh')\n environment = self.config.get('deploy_env', 'production')\n approval = self.config.get('deploy_approval', True)\n approvers = self.config.get('deploy_approvers', 'admin')\n self.pipeline_parts.append(StageTemplates.deploy_stage(\n environment, deploy_cmd, approval, approvers\n ))\n elif stage == 'docker-build':\n image_name = self.config.get('docker_image_name', 'myapp')\n dockerfile = self.config.get('dockerfile', 'Dockerfile')\n self.pipeline_parts.append(StageTemplates.docker_build_stage(image_name, dockerfile))\n elif stage == 'docker-push':\n image_name = self.config.get('docker_image_name', 'myapp')\n registry = self.config.get('docker_registry')\n registry_creds = self.config.get('docker_registry_credentials')\n self.pipeline_parts.append(StageTemplates.docker_push_stage(\n image_name, registry, registry_creds\n ))\n elif stage == 'parallel-tests':\n test_types = self.config.get('test_types', ['unit', 'integration'])\n # Avoid redundant failFast true at stage level when\n # parallelsAlwaysFailFast() is already set globally in options\n has_global_fail_fast = self.config.get('options', {}).get('parallelsAlwaysFailFast', False)\n stage_fail_fast = self.config.get('parallel_fail_fast', True) and not has_global_fail_fast\n self.pipeline_parts.append(StageTemplates.parallel_test_stage(\n test_types,\n fail_fast=stage_fail_fast,\n ))\n else:\n # Custom stage\n stage_name = ValidationHelpers.normalize_stage_name(\n stage.replace('-', ' ').replace('_', ' ').title()\n )\n stage_name_literal = GroovySyntax.single_quoted_literal(stage_name)\n custom_cmd = self.config.get(f'{stage}_cmd', f'echo \"Running {stage_name}\"')\n custom_cmd_literal = GroovySyntax.single_quoted_literal(custom_cmd)\n self.pipeline_parts.append(f\"\"\"\n stage({stage_name_literal}) {{\n steps {{\n sh {custom_cmd_literal}\n }}\n }}\"\"\")\n\n self.pipeline_parts.append(\" }\")\n\n def _add_post(self):\n \"\"\"Add post conditions\"\"\"\n artifacts = self.config.get('archive_artifacts')\n cleanup = self.config.get('cleanup', True)\n email = self.config.get('notification_email')\n slack = self.config.get('notification_slack')\n\n if email or slack:\n post_block = PostConditions.notification_post(\n email,\n slack,\n archive_artifacts=artifacts,\n cleanup=cleanup,\n )\n else:\n post_block = PostConditions.standard_post(artifacts, cleanup)\n\n if post_block:\n self.pipeline_parts.append(\"\")\n self.pipeline_parts.append(post_block)\n\n\ndef parse_args():\n \"\"\"Parse command line arguments\"\"\"\n parser = argparse.ArgumentParser(\n description='Generate Declarative Jenkins Pipeline',\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog='''\nExamples:\n # Basic CI pipeline\n %(prog)s --output Jenkinsfile --stages build,test --build-tool maven\n\n # Docker-based pipeline\n %(prog)s --output Jenkinsfile --agent docker --docker-image maven:3.9.9-eclipse-temurin-21\n\n # Full CD pipeline with deployment\n %(prog)s --output Jenkinsfile --stages checkout,build,test,deploy --deploy-env production\n\n # Kubernetes agent pipeline\n %(prog)s --output Jenkinsfile --agent kubernetes --k8s-yaml pod.yaml\n '''\n )\n\n # Required arguments\n parser.add_argument('--output', '-o', required=True,\n help='Output Jenkinsfile path')\n\n # Pipeline configuration\n parser.add_argument('--name', default='Generated Pipeline',\n help='Pipeline name (for header comment)')\n parser.add_argument('--stages', default='build,test',\n help='Comma-separated list of stages (build,test,deploy,etc.)')\n\n # Agent configuration\n parser.add_argument('--agent', default='any',\n choices=['any', 'none', 'label', 'docker', 'dockerfile', 'kubernetes'],\n help='Agent type')\n parser.add_argument('--agent-label', default='linux',\n help='Agent label (for --agent label)')\n parser.add_argument('--docker-image', default='ubuntu:latest',\n help='Docker image (for --agent docker)')\n parser.add_argument('--docker-args', default='',\n help='Docker arguments (for --agent docker)')\n parser.add_argument('--dockerfile', default='Dockerfile',\n help='Dockerfile name (for --agent dockerfile)')\n parser.add_argument('--k8s-yaml', default='',\n help='Kubernetes YAML (inline) or path to an existing file')\n\n # Build configuration\n parser.add_argument('--build-tool', default='maven',\n choices=['maven', 'gradle', 'npm', 'python', 'go'],\n help='Build tool (determines default commands)')\n parser.add_argument('--build-cmd', help='Custom build command')\n parser.add_argument('--test-cmd', help='Custom test command')\n parser.add_argument('--deploy-cmd', default='./deploy.sh',\n help='Deploy command')\n\n # SCM configuration\n parser.add_argument('--scm-url', help='Git repository URL')\n parser.add_argument('--branch', default='main', help='Git branch')\n parser.add_argument('--scm-credentials', help='SCM credentials ID')\n\n # Options\n parser.add_argument('--timeout', type=int, help='Pipeline timeout in hours')\n parser.add_argument('--build-discarder', type=int, default=10,\n help='Number of builds to keep')\n parser.add_argument('--disable-concurrent', action='store_true',\n help='Disable concurrent builds')\n parser.add_argument('--timestamps', action='store_true',\n help='Add timestamps to console output')\n parser.add_argument('--preserve-stashes', type=int, metavar='N',\n help='Preserve stashes for N builds (for stage restarting)')\n parser.add_argument('--durability-hint',\n choices=['PERFORMANCE_OPTIMIZED', 'SURVIVABLE_NONATOMIC', 'MAX_SURVIVABILITY'],\n help='Pipeline durability hint (trade performance for durability)')\n parser.add_argument('--quiet-period', type=int,\n help='Override global quiet period in seconds')\n parser.add_argument('--skip-stages-after-unstable', action='store_true',\n help='Skip remaining stages if build becomes unstable')\n parser.add_argument('--disable-resume', action='store_true',\n help='Do not allow pipeline to resume if controller restarts')\n parallel_fail_fast_group = parser.add_mutually_exclusive_group()\n parallel_fail_fast_group.add_argument('--parallels-fail-fast', dest='parallels_fail_fast',\n action='store_true',\n help='Abort all parallel stages when one fails (default)')\n parallel_fail_fast_group.add_argument('--no-parallels-fail-fast', dest='parallels_fail_fast',\n action='store_false',\n help='Allow parallel branches to continue after a failure')\n parser.set_defaults(parallels_fail_fast=True)\n\n # Deployment\n parser.add_argument('--deploy-env', default='production',\n help='Deployment environment name')\n parser.add_argument('--no-deploy-approval', action='store_true',\n help='Skip deployment approval')\n parser.add_argument('--deploy-approvers', default='admin',\n help='Comma-separated list of deployment approvers')\n\n # Notifications\n parser.add_argument('--notification-email', help='Email for notifications')\n parser.add_argument('--notification-slack', help='Slack channel for notifications')\n\n # Docker\n parser.add_argument('--docker-image-name', default='myapp',\n help='Docker image name for docker-build/push stages')\n parser.add_argument('--docker-registry', help='Docker registry URL')\n parser.add_argument('--docker-registry-credentials', help='Docker registry credentials ID')\n\n # Post-build\n parser.add_argument('--archive-artifacts', help='Artifacts pattern to archive')\n parser.add_argument('--no-cleanup', action='store_true',\n help='Disable workspace cleanup')\n\n return parser.parse_args()\n\n\ndef main():\n \"\"\"Main entry point\"\"\"\n args = parse_args()\n try:\n stages = ValidationHelpers.parse_stage_list(args.stages)\n k8s_yaml = resolve_k8s_yaml(args.k8s_yaml)\n except ValueError as exc:\n print(f\"ERROR: {exc}\", file=sys.stderr)\n return 2\n\n # Build configuration from args\n config = {\n 'name': args.name,\n 'stages': stages,\n 'agent': args.agent,\n 'agent_label': args.agent_label,\n 'docker_image': args.docker_image,\n 'docker_args': args.docker_args,\n 'dockerfile': args.dockerfile,\n 'k8s_yaml': k8s_yaml,\n 'build_tool': args.build_tool,\n 'scm_url': args.scm_url,\n 'branch': args.branch,\n 'scm_credentials': args.scm_credentials,\n 'deploy_cmd': args.deploy_cmd,\n 'deploy_env': args.deploy_env,\n 'deploy_approval': not args.no_deploy_approval,\n 'deploy_approvers': args.deploy_approvers,\n 'notification_email': args.notification_email,\n 'notification_slack': args.notification_slack,\n 'docker_image_name': args.docker_image_name,\n 'docker_registry': args.docker_registry,\n 'docker_registry_credentials': args.docker_registry_credentials,\n 'archive_artifacts': args.archive_artifacts,\n 'cleanup': not args.no_cleanup,\n 'parallel_fail_fast': args.parallels_fail_fast,\n }\n\n # Add custom commands if specified\n if args.build_cmd:\n config['build_cmd'] = args.build_cmd\n if args.test_cmd:\n config['test_cmd'] = args.test_cmd\n\n # Add options\n options = {}\n if args.timeout:\n options['timeout'] = {'time': args.timeout, 'unit': 'HOURS'}\n if args.build_discarder:\n options['buildDiscarder'] = {'numToKeepStr': str(args.build_discarder)}\n if args.disable_concurrent:\n options['disableConcurrentBuilds'] = True\n if args.timestamps:\n options['timestamps'] = True\n if args.preserve_stashes:\n options['preserveStashes'] = {'buildCount': args.preserve_stashes}\n if args.durability_hint:\n options['durabilityHint'] = args.durability_hint\n if args.quiet_period:\n options['quietPeriod'] = args.quiet_period\n if args.skip_stages_after_unstable:\n options['skipStagesAfterUnstable'] = True\n if args.disable_resume:\n options['disableResume'] = True\n if args.parallels_fail_fast and 'parallel-tests' in stages:\n options['parallelsAlwaysFailFast'] = True\n if options:\n config['options'] = options\n\n # Generate pipeline\n generator = DeclarativePipelineGenerator(config)\n jenkinsfile_content = generator.generate()\n\n # Write output\n output_path = Path(args.output)\n output_path.parent.mkdir(parents=True, exist_ok=True)\n output_path.write_text(jenkinsfile_content)\n\n print(f\"✓ Generated Declarative Jenkinsfile: {args.output}\")\n print(f\" Pipeline: {args.name}\")\n print(f\" Stages: {', '.join(config['stages'])}\")\n print(f\" Agent: {args.agent}\")\n print(\"\\n\" + \"=\"*60)\n print(\"NEXT STEP: Validate using jenkinsfile-validator skill\")\n print(f\" bash devops-skills-plugin/skills/jenkinsfile-validator/scripts/validate_jenkinsfile.sh {args.output}\")\n print(\"=\"*60)\n\n return 0\n\n\nif __name__ == '__main__':\n sys.exit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":19599,"content_sha256":"96f0b1460ff987361c9cff4811fb6f9c481afef8aaf6ff762a274bfe41e72624"},{"filename":"scripts/generate_scripted.py","content":"#!/usr/bin/env python3\n\"\"\"\nGenerate Scripted Jenkins Pipeline\n\nThis script generates a Scripted Jenkinsfile with specified configuration.\n\"\"\"\n\nimport argparse\nimport sys\nimport os\nfrom pathlib import Path\n\n# Add lib to path\nsys.path.insert(0, str(Path(__file__).parent / 'lib'))\n\nfrom common_patterns import PipelinePatterns\nfrom syntax_helpers import ScriptedSyntax, FormattingHelpers, ValidationHelpers\n\n\nclass ScriptedPipelineGenerator:\n \"\"\"Generator for Scripted Jenkins Pipelines\"\"\"\n\n def __init__(self, config):\n self.config = config\n self.pipeline_parts = []\n\n def generate(self):\n \"\"\"Generate complete scripted pipeline\"\"\"\n # Get agent/node configuration\n node_label = self.config.get('agent_label')\n\n # Build node content\n node_content = self._build_node_content()\n\n # Create node block\n if node_label:\n pipeline = ScriptedSyntax.node_block(node_label, node_content)\n else:\n pipeline = ScriptedSyntax.node_block(content=node_content)\n\n # Format and return\n return FormattingHelpers.format_jenkinsfile(\n FormattingHelpers.add_header_comment(\n pipeline,\n f\"Scripted Pipeline - {self.config.get('name', 'Generated Pipeline')}\"\n )\n )\n\n @staticmethod\n def _indent_lines(text, spaces=4):\n \"\"\"Indent all non-empty lines by the given number of spaces.\"\"\"\n prefix = ' ' * spaces\n return '\\n'.join(\n prefix + line if line.strip() else line\n for line in text.split('\\n')\n )\n\n def _build_node_content(self):\n \"\"\"Build content inside node block\"\"\"\n parts = []\n\n # Get stages\n stages = self.config.get('stages', ['build', 'test'])\n build_tool = self.config.get('build_tool', 'maven')\n pattern = PipelinePatterns.ci_pattern(build_tool)\n\n # Determine if we need try-catch-finally\n use_error_handling = self.config.get('error_handling', True)\n\n if use_error_handling:\n # Build try block content.\n # Re-indent by 4 extra spaces so stages sit inside try {} correctly.\n raw_try_content = self._build_stages_content(stages, pattern)\n try_content = self._indent_lines(raw_try_content, 4)\n\n # Build catch block\n catch_content = \"\"\" currentBuild.result = 'FAILURE'\n echo \"Pipeline failed: ${e.message}\"\n throw e\"\"\"\n\n # Build finally block (cleanup)\n finally_content = \"\"\n if self.config.get('cleanup', True):\n finally_content = \" deleteDir()\"\n\n # Add notification in catch if configured\n if self.config.get('notification_email'):\n catch_content = f\"\"\" currentBuild.result = 'FAILURE'\n emailext(\n subject: \"Build Failed: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}\",\n body: \"Error: ${{e.message}}\\\\nCheck console output at ${{env.BUILD_URL}}\",\n to: '{self.config.get('notification_email')}'\n )\n throw e\"\"\"\n\n node_content = ScriptedSyntax.try_catch_finally(\n try_content, catch_content, finally_content\n )\n else:\n # Simple stages without error handling\n node_content = self._build_stages_content(stages, pattern)\n if self.config.get('cleanup', True):\n node_content += \"\\n\\n deleteDir()\"\n\n return node_content\n\n def _build_stages_content(self, stages, pattern):\n \"\"\"Build stages content\"\"\"\n stage_blocks = []\n\n for stage in stages:\n if stage == 'checkout':\n stage_content = self._generate_checkout_stage()\n elif stage == 'build':\n stage_content = self._generate_build_stage(pattern)\n elif stage == 'test':\n stage_content = self._generate_test_stage(pattern)\n elif stage == 'deploy':\n stage_content = self._generate_deploy_stage()\n elif stage == 'docker-build':\n stage_content = self._generate_docker_build_stage()\n elif stage == 'docker-push':\n stage_content = self._generate_docker_push_stage()\n elif stage == 'parallel-tests':\n stage_content = self._generate_parallel_tests_stage()\n else:\n # Custom stage\n custom_cmd = self.config.get(f'{stage}_cmd', f'echo \"Running {stage}\"')\n stage_content = f\"\"\" sh '{custom_cmd}'\"\"\"\n\n stage_display_name = stage.replace('-', ' ').replace('_', ' ').title()\n stage_block = ScriptedSyntax.stage_block(\n stage_display_name,\n stage_content\n )\n stage_blocks.append(stage_block)\n\n return '\\n\\n'.join(stage_blocks)\n\n def _generate_checkout_stage(self):\n \"\"\"Generate checkout stage\"\"\"\n scm_url = self.config.get('scm_url')\n branch = self.config.get('branch', 'main')\n credentials = self.config.get('scm_credentials')\n\n if scm_url:\n if credentials:\n return f\"\"\" checkout scmGit(\n branches: [[name: '*/{branch}']],\n userRemoteConfigs: [[\n url: '{scm_url}',\n credentialsId: '{credentials}'\n ]]\n )\"\"\"\n else:\n return f\"\"\" git branch: '{branch}', url: '{scm_url}'\"\"\"\n else:\n return \"\"\" checkout scm\"\"\"\n\n def _generate_build_stage(self, pattern):\n \"\"\"Generate build stage\"\"\"\n build_cmd = self.config.get('build_cmd', pattern['build_cmd'])\n\n # Check if using Docker\n if self.config.get('docker_image'):\n docker_image = self.config.get('docker_image')\n docker_args = self.config.get('docker_args', '')\n content = f\"\"\" sh '{build_cmd}'\"\"\"\n\n return ScriptedSyntax.docker_inside_block(docker_image, content, docker_args)\n else:\n return f\"\"\" sh '{build_cmd}'\"\"\"\n\n def _generate_test_stage(self, pattern):\n \"\"\"Generate test stage\"\"\"\n test_cmd = self.config.get('test_cmd', pattern['test_cmd'])\n test_results = self.config.get('test_results', pattern['test_results'])\n\n content = f\"\"\" sh '{test_cmd}'\n junit '{test_results}'\"\"\"\n\n # Check if using Docker\n if self.config.get('docker_image'):\n docker_image = self.config.get('docker_image')\n docker_args = self.config.get('docker_args', '')\n return ScriptedSyntax.docker_inside_block(docker_image, content, docker_args)\n else:\n return content\n\n def _generate_deploy_stage(self):\n \"\"\"Generate deploy stage\"\"\"\n deploy_cmd = self.config.get('deploy_cmd', './deploy.sh')\n deploy_env = self.config.get('deploy_env', 'production')\n approval = self.config.get('deploy_approval', True)\n approvers = self.config.get('deploy_approvers', 'admin')\n\n if approval:\n return f\"\"\" input message: 'Deploy to {deploy_env}?', submitter: '{approvers}'\n sh '{deploy_cmd}'\"\"\"\n else:\n return f\"\"\" sh '{deploy_cmd}'\"\"\"\n\n def _generate_docker_build_stage(self):\n \"\"\"Generate Docker build stage\"\"\"\n image_name = self.config.get('docker_image_name', 'myapp')\n dockerfile = self.config.get('dockerfile', 'Dockerfile')\n\n return f\"\"\" def customImage = docker.build('{image_name}:${{BUILD_NUMBER}}', '-f {dockerfile} .')\"\"\"\n\n def _generate_docker_push_stage(self):\n \"\"\"Generate Docker push stage\"\"\"\n image_name = self.config.get('docker_image_name', 'myapp')\n registry = self.config.get('docker_registry')\n registry_creds = self.config.get('docker_registry_credentials')\n\n if registry and registry_creds:\n return f\"\"\" docker.withRegistry('{registry}', '{registry_creds}') {{\n docker.image('{image_name}:${{BUILD_NUMBER}}').push()\n docker.image('{image_name}:${{BUILD_NUMBER}}').push('latest')\n }}\"\"\"\n else:\n return f\"\"\" docker.image('{image_name}:${{BUILD_NUMBER}}').push()\n docker.image('{image_name}:${{BUILD_NUMBER}}').push('latest')\"\"\"\n\n def _generate_parallel_tests_stage(self):\n \"\"\"Generate parallel test stages\"\"\"\n test_types = self.config.get('test_types', ['unit', 'integration'])\n\n parallel_stages = {}\n for test_type in test_types:\n parallel_stages[f'{test_type.capitalize()} Tests'] = f\"\"\" sh 'npm run test:{test_type}'\"\"\"\n\n # Return just the content without the stage wrapper\n # because ScriptedSyntax.parallel_block will be wrapped in a stage\n parallel_content = []\n for name, content in parallel_stages.items():\n parallel_content.append(f\"\"\" '{name}': {{\n node {{\n{content}\n }}\n }}\"\"\")\n\n return f\"\"\" parallel(\n{','.join(parallel_content)}\n )\"\"\"\n\n\ndef parse_args():\n \"\"\"Parse command line arguments\"\"\"\n parser = argparse.ArgumentParser(\n description='Generate Scripted Jenkins Pipeline',\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog='''\nExamples:\n # Basic CI pipeline\n %(prog)s --output Jenkinsfile --stages build,test --build-tool maven\n\n # Docker-based pipeline\n %(prog)s --output Jenkinsfile --docker-image maven:3.9.9-eclipse-temurin-21\n\n # Full CD pipeline with deployment\n %(prog)s --output Jenkinsfile --stages checkout,build,test,deploy --deploy-env production\n\n # Pipeline with specific node label\n %(prog)s --output Jenkinsfile --agent-label linux-docker --stages build,test\n '''\n )\n\n # Required arguments\n parser.add_argument('--output', '-o', required=True,\n help='Output Jenkinsfile path')\n\n # Pipeline configuration\n parser.add_argument('--name', default='Generated Pipeline',\n help='Pipeline name (for header comment)')\n parser.add_argument('--stages', default='build,test',\n help='Comma-separated list of stages (build,test,deploy,etc.)')\n\n # Agent configuration\n parser.add_argument('--agent-label', help='Node label for agent selection')\n\n # Build configuration\n parser.add_argument('--build-tool', default='maven',\n choices=['maven', 'gradle', 'npm', 'python', 'go'],\n help='Build tool (determines default commands)')\n parser.add_argument('--build-cmd', help='Custom build command')\n parser.add_argument('--test-cmd', help='Custom test command')\n parser.add_argument('--deploy-cmd', default='./deploy.sh',\n help='Deploy command')\n\n # Docker configuration\n parser.add_argument('--docker-image', help='Docker image to use for build')\n parser.add_argument('--docker-args', default='',\n help='Docker run arguments')\n parser.add_argument('--docker-image-name', default='myapp',\n help='Docker image name for docker-build/push stages')\n parser.add_argument('--docker-registry', help='Docker registry URL')\n parser.add_argument('--docker-registry-credentials', help='Docker registry credentials ID')\n parser.add_argument('--dockerfile', default='Dockerfile',\n help='Dockerfile name')\n\n # SCM configuration\n parser.add_argument('--scm-url', help='Git repository URL')\n parser.add_argument('--branch', default='main', help='Git branch')\n parser.add_argument('--scm-credentials', help='SCM credentials ID')\n\n # Deployment\n parser.add_argument('--deploy-env', default='production',\n help='Deployment environment name')\n parser.add_argument('--no-deploy-approval', action='store_true',\n help='Skip deployment approval')\n parser.add_argument('--deploy-approvers', default='admin',\n help='Comma-separated list of deployment approvers')\n\n # Error handling and cleanup\n parser.add_argument('--no-error-handling', action='store_true',\n help='Disable try-catch-finally error handling')\n parser.add_argument('--no-cleanup', action='store_true',\n help='Disable workspace cleanup')\n\n # Notifications\n parser.add_argument('--notification-email', help='Email for notifications')\n\n # Test configuration\n parser.add_argument('--test-types', default='unit,integration',\n help='Comma-separated test types for parallel-tests stage')\n\n return parser.parse_args()\n\n\ndef main():\n \"\"\"Main entry point\"\"\"\n args = parse_args()\n\n try:\n stages = ValidationHelpers.parse_stage_list(args.stages)\n except ValueError as exc:\n print(f\"ERROR: {exc}\", file=sys.stderr)\n return 2\n\n # Build configuration from args\n config = {\n 'name': args.name,\n 'stages': stages,\n 'agent_label': args.agent_label,\n 'build_tool': args.build_tool,\n 'docker_image': args.docker_image,\n 'docker_args': args.docker_args,\n 'docker_image_name': args.docker_image_name,\n 'docker_registry': args.docker_registry,\n 'docker_registry_credentials': args.docker_registry_credentials,\n 'dockerfile': args.dockerfile,\n 'scm_url': args.scm_url,\n 'branch': args.branch,\n 'scm_credentials': args.scm_credentials,\n 'deploy_cmd': args.deploy_cmd,\n 'deploy_env': args.deploy_env,\n 'deploy_approval': not args.no_deploy_approval,\n 'deploy_approvers': args.deploy_approvers,\n 'error_handling': not args.no_error_handling,\n 'cleanup': not args.no_cleanup,\n 'notification_email': args.notification_email,\n 'test_types': args.test_types.split(','),\n }\n\n # Add custom commands if specified\n if args.build_cmd:\n config['build_cmd'] = args.build_cmd\n if args.test_cmd:\n config['test_cmd'] = args.test_cmd\n\n # Determine test results pattern\n pattern = PipelinePatterns.ci_pattern(args.build_tool)\n config['test_results'] = pattern['test_results']\n\n # Generate pipeline\n generator = ScriptedPipelineGenerator(config)\n jenkinsfile_content = generator.generate()\n\n # Write output\n output_path = Path(args.output)\n output_path.parent.mkdir(parents=True, exist_ok=True)\n output_path.write_text(jenkinsfile_content)\n\n print(f\"✓ Generated Scripted Jenkinsfile: {args.output}\")\n print(f\" Pipeline: {args.name}\")\n print(f\" Stages: {', '.join(config['stages'])}\")\n if args.agent_label:\n print(f\" Agent Label: {args.agent_label}\")\n print(\"\\n\" + \"=\"*60)\n print(\"NEXT STEP: Validate the generated Jenkinsfile\")\n print(\" Use: jenkinsfile-validator skill\")\n print(\"=\"*60)\n\n return 0\n\n\nif __name__ == '__main__':\n sys.exit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15073,"content_sha256":"ccfc79758c6d47f386851b053505ce7d58f25b7ccb315d9af974913de73fa444"},{"filename":"scripts/generate_shared_library.py","content":"#!/usr/bin/env python3\n\"\"\"\nJenkins Shared Library Scaffolding Generator\n\nGenerates a complete shared library structure following Jenkins best practices.\n\nUsage:\n python generate_shared_library.py --name my-jenkins-library --output ./output\n\nStructure generated:\n my-jenkins-library/\n ├── src/\n │ └── org/\n │ └── example/\n │ └── Utils.groovy\n ├── vars/\n │ ├── log.groovy\n │ ├── log.txt\n │ ├── buildApp.groovy\n │ ├── buildApp.txt\n │ ├── deployApp.groovy\n │ └── deployApp.txt\n ├── resources/\n │ └── org/\n │ └── example/\n │ └── config.json\n └── README.md\n\"\"\"\n\nimport argparse\nimport os\nfrom datetime import datetime\n\n\nclass SharedLibraryGenerator:\n \"\"\"Generates Jenkins Shared Library scaffolding\"\"\"\n\n def __init__(self, name, package='org.example', output_dir='.'):\n self.name = name\n self.package = package\n self.package_path = package.replace('.', '/')\n self.output_dir = os.path.join(output_dir, name)\n self.timestamp = datetime.now().strftime('%Y-%m-%d')\n\n def generate(self):\n \"\"\"Generate the complete shared library structure\"\"\"\n self._create_directory_structure()\n self._generate_src_files()\n self._generate_vars_files()\n self._generate_resources()\n self._generate_readme()\n print(f\"Shared library '{self.name}' generated at: {self.output_dir}\")\n\n def _create_directory_structure(self):\n \"\"\"Create the directory structure\"\"\"\n dirs = [\n f\"src/{self.package_path}\",\n \"vars\",\n f\"resources/{self.package_path}\",\n \"test/groovy\",\n ]\n for dir_path in dirs:\n os.makedirs(os.path.join(self.output_dir, dir_path), exist_ok=True)\n\n def _generate_src_files(self):\n \"\"\"Generate src/ Groovy class files\"\"\"\n # Utils.groovy\n utils_content = f'''package {self.package}\n\n/**\n * Utility class for common pipeline operations\n * Generated: {self.timestamp}\n */\nclass Utils implements Serializable {{\n\n /**\n * Get the current Git branch name\n */\n static String getBranchName(script) {{\n return script.sh(\n script: 'git rev-parse --abbrev-ref HEAD',\n returnStdout: true\n ).trim()\n }}\n\n /**\n * Get the current Git commit SHA\n */\n static String getCommitSha(script) {{\n return script.sh(\n script: 'git rev-parse HEAD',\n returnStdout: true\n ).trim()\n }}\n\n /**\n * Get short commit SHA (7 characters)\n */\n static String getShortCommitSha(script) {{\n return script.sh(\n script: 'git rev-parse --short HEAD',\n returnStdout: true\n ).trim()\n }}\n\n /**\n * Check if running on a specific branch\n */\n static boolean isMainBranch(script) {{\n def branch = getBranchName(script)\n return branch == 'main' || branch == 'master'\n }}\n\n /**\n * Validate required environment variables\n */\n static void validateEnvVars(script, List\u003cString> requiredVars) {{\n def missing = requiredVars.findAll {{ !script.env[it] }}\n if (missing) {{\n script.error(\"Missing required environment variables: ${{missing.join(', ')}}\")\n }}\n }}\n}}\n'''\n self._write_file(f\"src/{self.package_path}/Utils.groovy\", utils_content)\n\n # Docker.groovy\n docker_content = f'''package {self.package}\n\n/**\n * Docker utility class for container operations\n * Generated: {self.timestamp}\n */\nclass Docker implements Serializable {{\n\n private def script\n private String registry\n private String credentialsId\n\n Docker(script, String registry = null, String credentialsId = null) {{\n this.script = script\n this.registry = registry\n this.credentialsId = credentialsId\n }}\n\n /**\n * Build a Docker image\n */\n def build(String imageName, String tag = 'latest', String dockerfile = 'Dockerfile', String context = '.') {{\n def fullImageName = registry ? \"${{registry}}/${{imageName}}\" : imageName\n def imageTag = \"${{fullImageName}}:${{tag}}\"\n\n script.echo \"Building Docker image: ${{imageTag}}\"\n return script.docker.build(imageTag, \"-f ${{dockerfile}} ${{context}}\")\n }}\n\n /**\n * Push a Docker image to registry\n */\n def push(def image, String... tags) {{\n if (!registry) {{\n script.error(\"Registry not configured for push operation\")\n }}\n\n script.docker.withRegistry(\"https://${{registry}}\", credentialsId) {{\n tags.each {{ tag ->\n script.echo \"Pushing image with tag: ${{tag}}\"\n image.push(tag)\n }}\n }}\n }}\n\n /**\n * Build and push in one operation\n */\n def buildAndPush(String imageName, List\u003cString> tags = ['latest']) {{\n def image = build(imageName, tags[0])\n push(image, *tags)\n return image\n }}\n\n /**\n * Run a container for testing\n */\n def test(String imageName, String command) {{\n script.sh \"docker run --rm ${{imageName}} ${{command}}\"\n }}\n\n /**\n * Clean up dangling images\n */\n def cleanup() {{\n script.sh 'docker image prune -f || true'\n }}\n}}\n'''\n self._write_file(f\"src/{self.package_path}/Docker.groovy\", docker_content)\n\n # Notifications.groovy\n notifications_content = f'''package {self.package}\n\n/**\n * Notification utilities for pipeline status updates\n * Generated: {self.timestamp}\n */\nclass Notifications implements Serializable {{\n\n private def script\n\n Notifications(script) {{\n this.script = script\n }}\n\n /**\n * Send Slack notification\n */\n def slack(String channel, String message, String color = 'good') {{\n try {{\n script.slackSend(\n channel: channel,\n color: color,\n message: message\n )\n }} catch (Exception e) {{\n script.echo \"Warning: Failed to send Slack notification: ${{e.message}}\"\n }}\n }}\n\n /**\n * Send email notification\n */\n def email(String to, String subject, String body) {{\n try {{\n script.emailext(\n to: to,\n subject: subject,\n body: body,\n mimeType: 'text/html'\n )\n }} catch (Exception e) {{\n script.echo \"Warning: Failed to send email notification: ${{e.message}}\"\n }}\n }}\n\n /**\n * Send build status notification\n */\n def buildStatus(String channel = null, String email = null) {{\n def status = script.currentBuild.currentResult ?: 'SUCCESS'\n def color = status == 'SUCCESS' ? 'good' : 'danger'\n def message = \"\"\"\n *Build ${{status}}*\n Job: ${{script.env.JOB_NAME}} #${{script.env.BUILD_NUMBER}}\n Branch: ${{script.env.BRANCH_NAME ?: 'N/A'}}\n \u003c${{script.env.BUILD_URL}}|View Build>\n \"\"\".stripIndent().trim()\n\n if (channel) {{\n slack(channel, message, color)\n }}\n\n if (email) {{\n def subject = \"[${{status}}] ${{script.env.JOB_NAME}} #${{script.env.BUILD_NUMBER}}\"\n def htmlBody = \"\"\"\n \u003ch3>Build ${{status}}\u003c/h3>\n \u003cul>\n \u003cli>\u003cb>Job:\u003c/b> ${{script.env.JOB_NAME}} #${{script.env.BUILD_NUMBER}}\u003c/li>\n \u003cli>\u003cb>Branch:\u003c/b> ${{script.env.BRANCH_NAME ?: 'N/A'}}\u003c/li>\n \u003c/ul>\n \u003cp>\u003ca href=\"${{script.env.BUILD_URL}}\">View Build\u003c/a>\u003c/p>\n \"\"\".stripIndent().trim()\n this.email(email, subject, htmlBody)\n }}\n }}\n}}\n'''\n self._write_file(f\"src/{self.package_path}/Notifications.groovy\", notifications_content)\n\n def _generate_vars_files(self):\n \"\"\"Generate vars/ global variable files\"\"\"\n # log.groovy - Logging utilities\n log_groovy = '''/**\n * Logging utilities for Jenkins pipelines\n *\n * Usage:\n * @Library('my-jenkins-library') _\n * log.info 'Starting build...'\n * log.warning 'Deprecated feature used'\n * log.error 'Build failed!'\n */\n\ndef info(String message) {\n echo \"\\\\033[34m[INFO]\\\\033[0m ${message}\"\n}\n\ndef warning(String message) {\n echo \"\\\\033[33m[WARNING]\\\\033[0m ${message}\"\n}\n\ndef error(String message) {\n echo \"\\\\033[31m[ERROR]\\\\033[0m ${message}\"\n}\n\ndef success(String message) {\n echo \"\\\\033[32m[SUCCESS]\\\\033[0m ${message}\"\n}\n\ndef debug(String message) {\n if (env.DEBUG == 'true') {\n echo \"\\\\033[35m[DEBUG]\\\\033[0m ${message}\"\n }\n}\n'''\n self._write_file(\"vars/log.groovy\", log_groovy)\n self._write_file(\"vars/log.txt\", \"Logging utilities: log.info(), log.warning(), log.error(), log.success(), log.debug()\")\n\n # buildApp.groovy - Standard build function\n build_app_groovy = f'''/**\n * Standard application build function\n *\n * Usage:\n * @Library('{self.name}') _\n * buildApp(\n * buildTool: 'maven',\n * jdkVersion: 'JDK-21'\n * )\n */\n\ndef call(Map config = [:]) {{\n def buildTool = config.get('buildTool', 'maven')\n def jdkVersion = config.get('jdkVersion', 'JDK-21')\n def skipTests = config.get('skipTests', false)\n def additionalArgs = config.get('additionalArgs', '')\n\n pipeline {{\n agent any\n\n tools {{\n jdk jdkVersion\n }}\n\n stages {{\n stage('Checkout') {{\n steps {{\n checkout scm\n }}\n }}\n\n stage('Build') {{\n steps {{\n script {{\n switch(buildTool) {{\n case 'maven':\n def testFlag = skipTests ? '-DskipTests' : ''\n sh \"mvn clean package ${{testFlag}} ${{additionalArgs}}\"\n break\n case 'gradle':\n def testFlag = skipTests ? '-x test' : ''\n sh \"./gradlew clean build ${{testFlag}} ${{additionalArgs}}\"\n break\n case 'npm':\n sh 'npm ci'\n sh \"npm run build ${{additionalArgs}}\"\n if (!skipTests) {{\n sh 'npm test'\n }}\n break\n default:\n error \"Unknown build tool: ${{buildTool}}\"\n }}\n }}\n }}\n }}\n }}\n\n post {{\n always {{\n cleanWs()\n }}\n }}\n }}\n}}\n'''\n self._write_file(\"vars/buildApp.groovy\", build_app_groovy)\n self._write_file(\"vars/buildApp.txt\", \"Build application: buildApp(buildTool: 'maven', jdkVersion: 'JDK-21', skipTests: false)\")\n\n # deployApp.groovy - Deployment function\n deploy_app_groovy = f'''/**\n * Standard deployment function\n *\n * Usage:\n * @Library('{self.name}') _\n * deployApp(\n * environment: 'staging',\n * kubeConfig: 'kubeconfig-staging',\n * namespace: 'myapp-staging'\n * )\n */\n\ndef call(Map config = [:]) {{\n def environment = config.get('environment', 'dev')\n def kubeConfig = config.get('kubeConfig')\n def namespace = config.get('namespace', 'default')\n def rawDeploymentName = config.get('deploymentName', env.JOB_BASE_NAME ?: '')\n def deploymentName = rawDeploymentName\n .toString()\n .toLowerCase()\n .replaceAll(/[^a-z0-9-]/, '-')\n .replaceAll(/-+/, '-')\n .replaceAll(/^-|-$/, '')\n if (deploymentName.length() > 63) {{\n deploymentName = deploymentName.substring(0, 63).replaceAll(/-+$/, '')\n }}\n if (!deploymentName?.trim()) {{\n error \"deploymentName is required and must resolve to a valid Kubernetes deployment name\"\n }}\n def manifests = config.get('manifests', 'k8s/')\n def approval = config.get('approval', environment != 'dev')\n\n if (approval) {{\n stage(\"Approve Deploy to ${{environment}}\") {{\n input message: \"Deploy to ${{environment}}?\",\n ok: 'Deploy',\n submitter: 'admin,ops-team'\n }}\n }}\n\n stage(\"Deploy to ${{environment}}\") {{\n if (kubeConfig) {{\n withCredentials([file(credentialsId: kubeConfig, variable: 'KUBECONFIG')]) {{\n withEnv([\n \"KUBE_NAMESPACE=${{namespace}}\",\n \"DEPLOYMENT_NAME=${{deploymentName}}\",\n \"MANIFESTS_PATH=${{manifests}}\",\n ]) {{\n sh(label: 'Deploy manifests', script: \"\"\"\n set -euo pipefail\n kubectl apply -f \"$MANIFESTS_PATH\" -n \"$KUBE_NAMESPACE\"\n kubectl rollout status \"deployment/$DEPLOYMENT_NAME\" -n \"$KUBE_NAMESPACE\" --timeout=300s\n \"\"\")\n }}\n }}\n }} else {{\n error \"kubeConfig credential ID is required for deployment\"\n }}\n }}\n}}\n'''\n self._write_file(\"vars/deployApp.groovy\", deploy_app_groovy)\n self._write_file(\"vars/deployApp.txt\", \"Deploy application: deployApp(environment: 'staging', kubeConfig: 'kubeconfig-staging', namespace: 'myapp', deploymentName: 'myapp')\")\n\n # dockerBuild.groovy - Docker build and push\n docker_build_groovy = f'''/**\n * Docker build and push function\n *\n * Usage:\n * @Library('{self.name}') _\n * dockerBuild(\n * imageName: 'myapp',\n * registry: 'registry.example.com',\n * credentialsId: 'docker-registry-creds'\n * )\n */\n\ndef call(Map config = [:]) {{\n def imageName = config.get('imageName', env.JOB_NAME)\n def registry = config.get('registry')\n def credentialsId = config.get('credentialsId')\n def dockerfile = config.get('dockerfile', 'Dockerfile')\n def context = config.get('context', '.')\n def tags = config.get('tags', [env.BUILD_NUMBER, 'latest'])\n def push = config.get('push', true)\n\n def fullImageName = registry ? \"${{registry}}/${{imageName}}\" : imageName\n\n stage('Build Docker Image') {{\n script {{\n def image = docker.build(\"${{fullImageName}}:${{tags[0]}}\", \"-f ${{dockerfile}} ${{context}}\")\n\n // Tag with additional tags\n tags.drop(1).each {{ tag ->\n image.tag(tag)\n }}\n\n if (push && registry && credentialsId) {{\n stage('Push Docker Image') {{\n docker.withRegistry(\"https://${{registry}}\", credentialsId) {{\n tags.each {{ tag ->\n image.push(tag)\n }}\n }}\n }}\n }}\n\n return image\n }}\n }}\n}}\n'''\n self._write_file(\"vars/dockerBuild.groovy\", docker_build_groovy)\n self._write_file(\"vars/dockerBuild.txt\", \"Docker build: dockerBuild(imageName: 'myapp', registry: 'registry.example.com', credentialsId: 'docker-creds')\")\n\n # securityScan.groovy - Security scanning\n security_scan_groovy = f'''/**\n * Security scanning function\n *\n * Usage:\n * @Library('{self.name}') _\n * securityScan(\n * sonarqube: true,\n * owaspDependencyCheck: true,\n * trivy: true,\n * trivyImage: 'myapp:latest'\n * )\n */\n\ndef call(Map config = [:]) {{\n def sonarqube = config.get('sonarqube', false)\n def sonarqubeServer = config.get('sonarqubeServer', 'sonarqube-server')\n def owaspDependencyCheck = config.get('owaspDependencyCheck', false)\n def trivy = config.get('trivy', false)\n def trivyImage = config.get('trivyImage')\n def failOnCritical = config.get('failOnCritical', true)\n\n def scanStages = [:]\n\n if (sonarqube) {{\n scanStages['SonarQube Analysis'] = {{\n stage('SonarQube Analysis') {{\n withSonarQubeEnv(sonarqubeServer) {{\n sh 'mvn sonar:sonar'\n }}\n }}\n }}\n }}\n\n if (owaspDependencyCheck) {{\n scanStages['OWASP Dependency Check'] = {{\n stage('OWASP Dependency Check') {{\n dependencyCheck(\n additionalArguments: '--scan . --format HTML --format XML',\n odcInstallation: 'OWASP-Dependency-Check'\n )\n dependencyCheckPublisher(\n pattern: 'dependency-check-report.xml',\n failedTotalCritical: failOnCritical ? 0 : 999\n )\n }}\n }}\n }}\n\n if (trivy && trivyImage) {{\n scanStages['Trivy Container Scan'] = {{\n stage('Trivy Container Scan') {{\n def severityFlag = failOnCritical ? '--exit-code 1 --severity CRITICAL' : '--severity CRITICAL,HIGH'\n sh \"\"\"\n docker run --rm \\\\\n -v /var/run/docker.sock:/var/run/docker.sock \\\\\n aquasec/trivy:latest image \\\\\n ${{severityFlag}} \\\\\n --ignore-unfixed \\\\\n ${{trivyImage}}\n \"\"\"\n }}\n }}\n }}\n\n if (scanStages) {{\n parallel scanStages\n }}\n\n if (sonarqube) {{\n stage('Quality Gate') {{\n timeout(time: 5, unit: 'MINUTES') {{\n waitForQualityGate abortPipeline: true\n }}\n }}\n }}\n}}\n'''\n self._write_file(\"vars/securityScan.groovy\", security_scan_groovy)\n self._write_file(\"vars/securityScan.txt\", \"Security scanning: securityScan(sonarqube: true, owaspDependencyCheck: true, trivy: true, trivyImage: 'myapp:latest')\")\n\n def _generate_resources(self):\n \"\"\"Generate resources/ files\"\"\"\n config_json = '''{\n \"environments\": {\n \"dev\": {\n \"namespace\": \"dev\",\n \"replicas\": 1\n },\n \"staging\": {\n \"namespace\": \"staging\",\n \"replicas\": 2\n },\n \"production\": {\n \"namespace\": \"production\",\n \"replicas\": 3\n }\n },\n \"notifications\": {\n \"slack\": {\n \"channel\": \"#builds\"\n },\n \"email\": {\n \"recipients\": \"[email protected]\"\n }\n }\n}\n'''\n self._write_file(f\"resources/{self.package_path}/config.json\", config_json)\n\n def _generate_readme(self):\n \"\"\"Generate README.md\"\"\"\n readme = f'''# {self.name}\n\nJenkins Shared Library for CI/CD pipelines.\n\nGenerated: {self.timestamp}\n\n## Directory Structure\n\n```\n{self.name}/\n├── src/ # Groovy source files (classes)\n│ └── {self.package_path}/\n│ ├── Utils.groovy # Utility class\n│ ├── Docker.groovy # Docker operations\n│ └── Notifications.groovy # Notification utilities\n├── vars/ # Global pipeline variables\n│ ├── log.groovy # Logging utilities\n│ ├── buildApp.groovy # Build function\n│ ├── deployApp.groovy # Deployment function\n│ ├── dockerBuild.groovy # Docker build/push\n│ └── securityScan.groovy # Security scanning\n├── resources/ # Resource files\n│ └── {self.package_path}/\n│ └── config.json # Configuration data\n└── README.md\n```\n\n## Installation\n\n### Global Configuration\n\n1. Go to **Manage Jenkins** → **System Configuration** → **Global Pipeline Libraries**\n2. Add a new library:\n - **Name**: `{self.name}`\n - **Default version**: `main`\n - **Retrieval method**: Modern SCM → Git\n - **Project Repository**: `\u003cyour-git-repo-url>`\n\n### Per-Pipeline Configuration\n\n```groovy\n// Jenkinsfile\nlibrary identifier: '{self.name}@main', retriever: modernSCM([\n $class: 'GitSCMSource',\n remote: '\u003cyour-git-repo-url>',\n credentialsId: 'git-credentials'\n])\n```\n\n## Usage\n\n### Import the Library\n\n```groovy\n@Library('{self.name}') _\n```\n\nOr with a specific version:\n\n```groovy\n@Library('{self.name}@v1.0.0') _\n```\n\n### Available Functions\n\n#### Logging\n\n```groovy\nlog.info 'Starting build...'\nlog.warning 'Deprecated feature used'\nlog.error 'Build failed!'\nlog.success 'Build completed!'\nlog.debug 'Debug message (only shown when DEBUG=true)'\n```\n\n#### Build Application\n\n```groovy\nbuildApp(\n buildTool: 'maven', // 'maven', 'gradle', or 'npm'\n jdkVersion: 'JDK-21',\n skipTests: false,\n additionalArgs: ''\n)\n```\n\n#### Deploy Application\n\n```groovy\ndeployApp(\n environment: 'staging',\n kubeConfig: 'kubeconfig-staging',\n namespace: 'myapp-staging',\n manifests: 'k8s/',\n approval: true\n)\n```\n\n#### Docker Build and Push\n\n```groovy\ndockerBuild(\n imageName: 'myapp',\n registry: 'registry.example.com',\n credentialsId: 'docker-registry-creds',\n dockerfile: 'Dockerfile',\n context: '.',\n tags: [env.BUILD_NUMBER, 'latest'],\n push: true\n)\n```\n\n#### Security Scanning\n\n```groovy\nsecurityScan(\n sonarqube: true,\n sonarqubeServer: 'sonarqube-server',\n owaspDependencyCheck: true,\n trivy: true,\n trivyImage: 'myapp:latest',\n failOnCritical: true\n)\n```\n\n### Using Classes\n\n```groovy\n@Library('{self.name}') _\nimport {self.package}.Utils\nimport {self.package}.Docker\nimport {self.package}.Notifications\n\npipeline {{\n agent any\n\n stages {{\n stage('Example') {{\n steps {{\n script {{\n // Use utility functions\n def branch = Utils.getBranchName(this)\n def sha = Utils.getShortCommitSha(this)\n\n echo \"Building ${{branch}} at ${{sha}}\"\n\n // Use Docker class\n def docker = new Docker(this, 'registry.example.com', 'docker-creds')\n def image = docker.build('myapp', sha)\n docker.push(image, sha, 'latest')\n\n // Use Notifications\n def notify = new Notifications(this)\n notify.slack('#builds', \"Build completed: ${{sha}}\")\n }}\n }}\n }}\n }}\n}}\n```\n\n### Loading Resources\n\n```groovy\n@Library('{self.name}') _\n\npipeline {{\n agent any\n\n stages {{\n stage('Load Config') {{\n steps {{\n script {{\n def config = libraryResource '{self.package_path}/config.json'\n def configMap = readJSON text: config\n echo \"Production replicas: ${{configMap.environments.production.replicas}}\"\n }}\n }}\n }}\n }}\n}}\n```\n\n## Testing\n\nUnit tests for shared library functions can be added to `test/groovy/`.\n\nSee [Jenkins Pipeline Unit Testing Framework](https://github.com/jenkinsci/JenkinsPipelineUnit) for details.\n\n## Contributing\n\n1. Create a feature branch\n2. Make your changes\n3. Add/update tests\n4. Submit a pull request\n\n## License\n\nMIT License\n'''\n self._write_file(\"README.md\", readme)\n\n def _write_file(self, relative_path, content):\n \"\"\"Write content to a file\"\"\"\n file_path = os.path.join(self.output_dir, relative_path)\n os.makedirs(os.path.dirname(file_path), exist_ok=True)\n with open(file_path, 'w') as f:\n f.write(content)\n print(f\" Created: {relative_path}\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='Generate Jenkins Shared Library scaffolding',\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog='''\nExamples:\n %(prog)s --name my-jenkins-library\n %(prog)s --name my-jenkins-library --package com.mycompany.jenkins\n %(prog)s --name my-jenkins-library --output ./output\n '''\n )\n parser.add_argument(\n '--name', '-n',\n required=True,\n help='Name of the shared library'\n )\n parser.add_argument(\n '--package', '-p',\n default='org.example',\n help='Java package name for src/ classes (default: org.example)'\n )\n parser.add_argument(\n '--output', '-o',\n default='.',\n help='Output directory (default: current directory)'\n )\n\n args = parser.parse_args()\n\n generator = SharedLibraryGenerator(\n name=args.name,\n package=args.package,\n output_dir=args.output\n )\n generator.generate()\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":24740,"content_sha256":"93d0c1dc473f5673832c26340ad610c8f7bb96e221e798036f6193594d75dedf"},{"filename":"scripts/lib/__init__.py","content":"\"\"\"\nShared utilities for Jenkinsfile generation\n\"\"\"\n\nfrom .common_patterns import (\n PipelinePatterns,\n StageTemplates,\n PostConditions,\n EnvironmentTemplates,\n)\n\nfrom .syntax_helpers import (\n GroovySyntax,\n DeclarativeSyntax,\n ScriptedSyntax,\n ValidationHelpers,\n FormattingHelpers,\n)\n\n__all__ = [\n # Common patterns\n 'PipelinePatterns',\n 'StageTemplates',\n 'PostConditions',\n 'EnvironmentTemplates',\n # Syntax helpers\n 'GroovySyntax',\n 'DeclarativeSyntax',\n 'ScriptedSyntax',\n 'ValidationHelpers',\n 'FormattingHelpers',\n]\n","content_type":"text/x-python; charset=utf-8","language":"python","size":585,"content_sha256":"5b895121b4ce0c1dbc8a4f4fd0881b6aa693fdc8e46807b0b8fc8ea2989b47ed"},{"filename":"scripts/lib/common_patterns.py","content":"\"\"\"\nCommon Jenkins pipeline patterns and templates\n\"\"\"\n\ntry:\n from .syntax_helpers import GroovySyntax, ValidationHelpers\nexcept ImportError:\n from syntax_helpers import GroovySyntax, ValidationHelpers\n\n\nclass PipelinePatterns:\n \"\"\"Common pipeline pattern templates\"\"\"\n\n @staticmethod\n def ci_pattern(build_tool='maven'):\n \"\"\"Standard CI pattern: checkout -> build -> test -> archive\"\"\"\n patterns = {\n 'maven': {\n 'build_cmd': 'mvn clean compile',\n 'test_cmd': 'mvn test',\n 'package_cmd': 'mvn package',\n 'test_results': '**/target/surefire-reports/*.xml',\n 'artifacts': '**/target/*.jar',\n 'docker_image': 'maven:3.9.11-eclipse-temurin-21',\n 'tool': 'maven'\n },\n 'gradle': {\n 'build_cmd': './gradlew clean build',\n 'test_cmd': './gradlew test',\n 'package_cmd': './gradlew assemble',\n 'test_results': '**/build/test-results/**/*.xml',\n 'artifacts': '**/build/libs/*.jar',\n 'docker_image': 'gradle:9.1-jdk21',\n 'tool': 'gradle'\n },\n 'npm': {\n 'build_cmd': 'npm ci && npm run build',\n 'test_cmd': 'npm test',\n 'package_cmd': 'npm pack',\n 'test_results': '**/test-results/*.xml',\n 'artifacts': '**/*.tgz',\n 'docker_image': 'node:22-alpine',\n 'tool': 'nodejs'\n },\n 'python': {\n 'build_cmd': 'pip install -r requirements.txt',\n 'test_cmd': 'pytest --junitxml=test-results.xml',\n 'package_cmd': 'python setup.py sdist bdist_wheel',\n 'test_results': '**/test-results.xml',\n 'artifacts': 'dist/*',\n 'docker_image': 'python:3.12-slim-bookworm',\n 'tool': 'python'\n },\n 'go': {\n 'build_cmd': 'go build ./...',\n 'test_cmd': 'go test -v ./... | go-junit-report > test-results.xml',\n 'package_cmd': 'go build -o app',\n 'test_results': 'test-results.xml',\n 'artifacts': 'app',\n 'docker_image': 'golang:1.23-alpine',\n 'tool': 'go'\n }\n }\n return patterns.get(build_tool.lower(), patterns['maven'])\n\n @staticmethod\n def cd_pattern():\n \"\"\"Standard CD pattern: build -> test -> deploy (dev/staging/prod)\"\"\"\n return {\n 'environments': ['dev', 'staging', 'production'],\n 'approval_required': ['staging', 'production'],\n 'approvers': 'admin,ops-team'\n }\n\n @staticmethod\n def docker_build_pattern():\n \"\"\"Docker build and push pattern\"\"\"\n return {\n 'build_cmd': 'docker build -t ${DOCKER_IMAGE}:${BUILD_NUMBER} .',\n 'tag_latest': 'docker tag ${DOCKER_IMAGE}:${BUILD_NUMBER} ${DOCKER_IMAGE}:latest',\n 'push_cmd': 'docker push ${DOCKER_IMAGE}:${BUILD_NUMBER}',\n 'push_latest': 'docker push ${DOCKER_IMAGE}:latest',\n 'test_cmd': 'docker run --rm ${DOCKER_IMAGE}:${BUILD_NUMBER} test'\n }\n\n @staticmethod\n def kubernetes_deploy_pattern():\n \"\"\"Kubernetes deployment pattern\"\"\"\n return {\n 'update_image': 'kubectl set image deployment/${DEPLOYMENT_NAME} ${CONTAINER_NAME}=${IMAGE}:${TAG}',\n 'apply_manifests': 'kubectl apply -f k8s/',\n 'rollout_status': 'kubectl rollout status deployment/${DEPLOYMENT_NAME}',\n 'rollback': 'kubectl rollout undo deployment/${DEPLOYMENT_NAME}'\n }\n\n @staticmethod\n def parallel_test_pattern():\n \"\"\"Parallel test execution pattern\"\"\"\n return {\n 'test_types': ['unit', 'integration', 'e2e'],\n 'browsers': ['chrome', 'firefox', 'safari'],\n 'platforms': ['linux', 'windows', 'mac']\n }\n\n @staticmethod\n def performance_pattern(level='balanced'):\n \"\"\"Pipeline performance optimization patterns with durability hints.\n\n Args:\n level: 'fast', 'balanced', or 'durable'\n - fast: Maximum performance (PERFORMANCE_OPTIMIZED) - use for basic build-test pipelines\n - balanced: Faster but survives most crashes (SURVIVABLE_NONATOMIC)\n - durable: Maximum durability (MAX_SURVIVABILITY) - use for mission-critical pipelines\n\n Returns:\n dict: Configuration options for the specified performance level\n \"\"\"\n patterns = {\n 'fast': {\n 'durability_hint': 'PERFORMANCE_OPTIMIZED',\n 'description': 'Maximum performance, minimal persistence (2-6x faster). Use for pipelines that can be safely re-run.',\n 'options': \"\"\"options {\n durabilityHint('PERFORMANCE_OPTIMIZED')\n buildDiscarder(logRotator(numToKeepStr: '10'))\n timeout(time: 1, unit: 'HOURS')\n timestamps()\n}\"\"\"\n },\n 'balanced': {\n 'durability_hint': 'SURVIVABLE_NONATOMIC',\n 'description': 'Faster but survives most Jenkins crashes. Good balance for most pipelines.',\n 'options': \"\"\"options {\n durabilityHint('SURVIVABLE_NONATOMIC')\n buildDiscarder(logRotator(numToKeepStr: '20'))\n timeout(time: 2, unit: 'HOURS')\n timestamps()\n preserveStashes(buildCount: 5)\n}\"\"\"\n },\n 'durable': {\n 'durability_hint': 'MAX_SURVIVABILITY',\n 'description': 'Maximum durability (default, slowest). Use for mission-critical audit pipelines.',\n 'options': \"\"\"options {\n durabilityHint('MAX_SURVIVABILITY')\n buildDiscarder(logRotator(numToKeepStr: '50', daysToKeepStr: '90'))\n timeout(time: 4, unit: 'HOURS')\n timestamps()\n preserveStashes(buildCount: 10)\n}\"\"\"\n }\n }\n return patterns.get(level.lower(), patterns['balanced'])\n\n\nclass StageTemplates:\n \"\"\"Pre-built stage templates\"\"\"\n\n @staticmethod\n def checkout_stage(scm_url=None, branch='main', credentials=None):\n \"\"\"Generate checkout stage\"\"\"\n branch_pattern_literal = GroovySyntax.single_quoted_literal(f'*/{branch}')\n branch_literal = GroovySyntax.single_quoted_literal(branch)\n if scm_url:\n scm_url_literal = GroovySyntax.single_quoted_literal(scm_url)\n if credentials:\n credentials_literal = GroovySyntax.single_quoted_literal(credentials)\n return f\"\"\"\n stage('Checkout') {{\n steps {{\n checkout scmGit(\n branches: [[name: {branch_pattern_literal}]],\n userRemoteConfigs: [[\n url: {scm_url_literal},\n credentialsId: {credentials_literal}\n ]]\n )\n }}\n }}\"\"\"\n else:\n return f\"\"\"\n stage('Checkout') {{\n steps {{\n git branch: {branch_literal}, url: {scm_url_literal}\n }}\n }}\"\"\"\n else:\n return \"\"\"\n stage('Checkout') {\n steps {\n checkout scm\n }\n }\"\"\"\n\n @staticmethod\n def build_stage(build_cmd):\n \"\"\"Generate build stage\"\"\"\n build_cmd_literal = GroovySyntax.single_quoted_literal(build_cmd)\n return f\"\"\"\n stage('Build') {{\n steps {{\n sh {build_cmd_literal}\n }}\n }}\"\"\"\n\n @staticmethod\n def test_stage(test_cmd, test_results='**/test-results/*.xml'):\n \"\"\"Generate test stage with reporting\"\"\"\n test_cmd_literal = GroovySyntax.single_quoted_literal(test_cmd)\n test_results_literal = GroovySyntax.single_quoted_literal(test_results)\n return f\"\"\"\n stage('Test') {{\n steps {{\n sh {test_cmd_literal}\n }}\n post {{\n always {{\n junit {test_results_literal}\n }}\n }}\n }}\"\"\"\n\n @staticmethod\n def deploy_stage(environment, deploy_cmd, approval=False, approvers='admin'):\n \"\"\"Generate deployment stage with optional approval\"\"\"\n stage_name = ValidationHelpers.normalize_stage_name(\n f\"Deploy to {str(environment).strip().title()}\"\n )\n stage_name_literal = GroovySyntax.single_quoted_literal(stage_name)\n deploy_message_literal = GroovySyntax.single_quoted_literal(\n f\"Deploy to {str(environment).strip()}?\"\n )\n approvers_literal = GroovySyntax.single_quoted_literal(approvers)\n deploy_cmd_literal = GroovySyntax.single_quoted_literal(deploy_cmd)\n if approval:\n return f\"\"\"\n stage({stage_name_literal}) {{\n input {{\n message {deploy_message_literal}\n ok 'Deploy'\n submitter {approvers_literal}\n }}\n steps {{\n sh {deploy_cmd_literal}\n }}\n }}\"\"\"\n else:\n return f\"\"\"\n stage({stage_name_literal}) {{\n steps {{\n sh {deploy_cmd_literal}\n }}\n }}\"\"\"\n\n @staticmethod\n def docker_build_stage(image_name, dockerfile='Dockerfile'):\n \"\"\"Generate Docker build stage\"\"\"\n image_literal = GroovySyntax.single_quoted_literal(f'{image_name}:${{BUILD_NUMBER}}')\n build_args_literal = GroovySyntax.single_quoted_literal(f'-f {dockerfile} .')\n return f\"\"\"\n stage('Docker Build') {{\n steps {{\n script {{\n docker.build({image_literal}, {build_args_literal})\n }}\n }}\n }}\"\"\"\n\n @staticmethod\n def docker_push_stage(image_name, registry=None, credentials=None):\n \"\"\"Generate Docker push stage\"\"\"\n image_literal = GroovySyntax.single_quoted_literal(f'{image_name}:${{BUILD_NUMBER}}')\n if registry and credentials:\n registry_literal = GroovySyntax.single_quoted_literal(registry)\n credentials_literal = GroovySyntax.single_quoted_literal(credentials)\n return f\"\"\"\n stage('Docker Push') {{\n steps {{\n script {{\n docker.withRegistry({registry_literal}, {credentials_literal}) {{\n docker.image({image_literal}).push()\n docker.image({image_literal}).push('latest')\n }}\n }}\n }}\n }}\"\"\"\n else:\n return f\"\"\"\n stage('Docker Push') {{\n steps {{\n script {{\n docker.image({image_literal}).push()\n docker.image({image_literal}).push('latest')\n }}\n }}\n }}\"\"\"\n\n @staticmethod\n def parallel_test_stage(test_types=['unit', 'integration'], fail_fast=True):\n \"\"\"Generate parallel test stages\"\"\"\n parallel_stages = []\n for test_type in test_types:\n stage_name = ValidationHelpers.normalize_stage_name(f'{test_type.title()} Tests')\n stage_name_literal = GroovySyntax.single_quoted_literal(stage_name)\n test_cmd_literal = GroovySyntax.single_quoted_literal(f'npm run test:{test_type}')\n parallel_stages.append(f\"\"\"\n stage({stage_name_literal}) {{\n steps {{\n sh {test_cmd_literal}\n }}\n }}\"\"\")\n\n stages_str = '\\n'.join(parallel_stages)\n fail_fast_line = \" failFast true\\n\" if fail_fast else \"\"\n return f\"\"\"\n stage('Parallel Tests') {{\n{fail_fast_line}\n parallel {{\n{stages_str}\n }}\n }}\"\"\"\n\n\nclass PostConditions:\n \"\"\"Common post-condition templates\"\"\"\n\n @staticmethod\n def standard_post(archive_artifacts=None, cleanup=True):\n \"\"\"Standard post conditions\"\"\"\n post_blocks = []\n\n if cleanup:\n post_blocks.append(\"\"\"\n always {\n deleteDir()\n }\"\"\")\n\n if archive_artifacts:\n artifacts_literal = GroovySyntax.single_quoted_literal(archive_artifacts)\n post_blocks.append(f\"\"\"\n success {{\n archiveArtifacts artifacts: {artifacts_literal}, fingerprint: true\n }}\"\"\")\n\n post_blocks.append(\"\"\"\n failure {\n echo 'Pipeline failed!'\n }\"\"\")\n\n blocks_str = '\\n'.join(post_blocks)\n return f\"\"\"\n post {{\n{blocks_str}\n }}\"\"\"\n\n @staticmethod\n def notification_post(email=None, slack=None, archive_artifacts=None, cleanup=True):\n \"\"\"Post conditions with notifications and optional artifact archiving.\"\"\"\n post_blocks = []\n success_steps = []\n failure_steps = []\n\n if archive_artifacts:\n artifacts_literal = GroovySyntax.single_quoted_literal(archive_artifacts)\n success_steps.append(\n f\" archiveArtifacts artifacts: {artifacts_literal}, fingerprint: true\"\n )\n\n if email:\n email_literal = GroovySyntax.single_quoted_literal(email)\n success_steps.extend([\n \" emailext(\",\n \" subject: \\\"Build Succeeded: ${env.JOB_NAME} #${env.BUILD_NUMBER}\\\",\",\n \" body: \\\"Check console output at ${env.BUILD_URL}\\\",\",\n f\" to: {email_literal}\",\n \" )\",\n ])\n failure_steps.extend([\n \" emailext(\",\n \" subject: \\\"Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}\\\",\",\n \" body: \\\"Check console output at ${env.BUILD_URL}\\\",\",\n f\" to: {email_literal}\",\n \" )\",\n ])\n\n if slack:\n slack_literal = GroovySyntax.single_quoted_literal(slack)\n success_steps.append(\n f\" slackSend(color: 'good', message: \\\"Build ${{env.BUILD_NUMBER}} succeeded\\\", channel: {slack_literal})\"\n )\n failure_steps.append(\n f\" slackSend(color: 'danger', message: \\\"Build ${{env.BUILD_NUMBER}} failed\\\", channel: {slack_literal})\"\n )\n\n if success_steps:\n post_blocks.append(\" success {\\n\" + \"\\n\".join(success_steps) + \"\\n }\")\n\n if failure_steps:\n post_blocks.append(\" failure {\\n\" + \"\\n\".join(failure_steps) + \"\\n }\")\n else:\n post_blocks.append(\"\"\"\n failure {\n echo 'Pipeline failed!'\n }\"\"\")\n\n if cleanup:\n post_blocks.append(\"\"\"\n always {\n deleteDir()\n }\"\"\")\n\n blocks_str = '\\n'.join(post_blocks)\n return f\"\"\"\n post {{\n{blocks_str}\n }}\"\"\"\n\n\nclass EnvironmentTemplates:\n \"\"\"Environment variable templates\"\"\"\n\n @staticmethod\n def standard_env():\n \"\"\"Standard environment variables\"\"\"\n return \"\"\"\n environment {\n BUILD_ENV = 'production'\n JAVA_HOME = tool name: 'JDK-21', type: 'jdk'\n }\"\"\"\n\n @staticmethod\n def docker_env(registry=None):\n \"\"\"Docker-related environment variables\"\"\"\n if registry:\n return f\"\"\"\n environment {{\n DOCKER_REGISTRY = '{registry}'\n DOCKER_IMAGE = '${{DOCKER_REGISTRY}}/${{JOB_NAME}}'\n }}\"\"\"\n else:\n return \"\"\"\n environment {\n DOCKER_IMAGE = \"${JOB_NAME}\"\n }\"\"\"\n\n @staticmethod\n def aws_env():\n \"\"\"AWS-related environment variables with credentials\"\"\"\n return \"\"\"\n environment {\n AWS_DEFAULT_REGION = 'us-east-1'\n AWS_CREDENTIALS = credentials('aws-credentials')\n }\"\"\"\n\n @staticmethod\n def kubernetes_env():\n \"\"\"Kubernetes-related environment variables\"\"\"\n return \"\"\"\n environment {\n KUBECONFIG = credentials('kubeconfig')\n DEPLOYMENT_NAME = 'myapp'\n NAMESPACE = 'production'\n }\"\"\"\n","content_type":"text/x-python; charset=utf-8","language":"python","size":16254,"content_sha256":"260d3481dc9eda5854bd016c73dff4cc802159b2f4101b415e71d89f0e439b61"},{"filename":"scripts/lib/syntax_helpers.py","content":"\"\"\"\nHelper functions for generating proper Groovy/Jenkins syntax\n\"\"\"\n\nimport re\nfrom typing import List, Dict, Optional\n\n\nclass GroovySyntax:\n \"\"\"Helper functions for generating Groovy syntax\"\"\"\n\n @staticmethod\n def format_string(value: str) -> str:\n \"\"\"Format a string with proper Groovy quoting\"\"\"\n # Always escape backslashes first to avoid broken Groovy escape sequences\n escaped = value.replace('\\\\', '\\\\\\\\')\n if \"'\" in value and '\"' not in value:\n return f'\"{escaped}\"'\n elif '\"' in value and \"'\" not in value:\n return f\"'{escaped}'\"\n elif '\"' in value and \"'\" in value:\n # Use triple quotes\n return f\"'''{escaped}'''\"\n else:\n return f\"'{escaped}'\"\n\n @staticmethod\n def single_quoted_literal(value: str) -> str:\n \"\"\"Return a safe single-quoted Groovy literal.\"\"\"\n text = '' if value is None else str(value)\n escaped = text.replace('\\\\', '\\\\\\\\').replace(\"'\", \"\\\\'\")\n return f\"'{escaped}'\"\n\n @staticmethod\n def format_list(items: List[str]) -> str:\n \"\"\"Format a list in Groovy syntax\"\"\"\n formatted_items = [GroovySyntax.format_string(item) for item in items]\n return f\"[{', '.join(formatted_items)}]\"\n\n @staticmethod\n def format_map(data: Dict[str, str]) -> str:\n \"\"\"Format a map/dict in Groovy syntax\"\"\"\n formatted_items = [f\"{k}: {GroovySyntax.format_string(v)}\" for k, v in data.items()]\n return f\"[{', '.join(formatted_items)}]\"\n\n @staticmethod\n def format_multiline_string(text: str, indent: int = 0) -> str:\n \"\"\"Format multiline string with proper indentation\"\"\"\n lines = text.strip().split('\\n')\n indent_str = ' ' * indent\n formatted_lines = [f\"{indent_str}{line}\" for line in lines]\n return '\\n'.join(formatted_lines)\n\n @staticmethod\n def escape_groovy_string(text: str) -> str:\n \"\"\"Escape special characters in Groovy strings\"\"\"\n # Escape backslashes first\n text = text.replace('\\\\', '\\\\\\\\')\n # Escape quotes\n text = text.replace(\"'\", \"\\\\'\")\n # Escape dollar signs (for GString interpolation)\n text = text.replace('

Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…

, '\\\\

Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…

)\n return text\n\n @staticmethod\n def format_closure(content: str, indent: int = 0) -> str:\n \"\"\"Format a Groovy closure with proper bracing\"\"\"\n indent_str = ' ' * indent\n return f\"{indent_str}{{\\n{content}\\n{indent_str}}}\"\n\n\nclass DeclarativeSyntax:\n \"\"\"Helper functions specific to Declarative Pipeline syntax\"\"\"\n\n @staticmethod\n def agent_block(agent_type: str, **kwargs) -> str:\n \"\"\"Generate agent block\"\"\"\n if agent_type == 'any':\n return \" agent any\"\n elif agent_type == 'none':\n return \" agent none\"\n elif agent_type == 'label':\n label = kwargs.get('label', 'linux')\n return f\" agent {{ label '{label}' }}\"\n elif agent_type == 'docker':\n image = kwargs.get('image', 'ubuntu:latest')\n args = kwargs.get('args', '')\n reuseNode = kwargs.get('reuseNode', False)\n\n agent_config = [f\"image '{image}'\"]\n if args:\n agent_config.append(f\"args '{args}'\")\n if reuseNode:\n agent_config.append(\"reuseNode true\")\n\n config_str = '\\n '.join(agent_config)\n return f\"\"\" agent {{\n docker {{\n {config_str}\n }}\n }}\"\"\"\n elif agent_type == 'dockerfile':\n filename = kwargs.get('filename', 'Dockerfile')\n dir_path = kwargs.get('dir', '.')\n additionalBuildArgs = kwargs.get('additionalBuildArgs', '')\n\n if filename == 'Dockerfile' and dir_path == '.' and not additionalBuildArgs:\n return \" agent { dockerfile true }\"\n else:\n agent_config = []\n if filename != 'Dockerfile':\n agent_config.append(f\"filename '{filename}'\")\n if dir_path != '.':\n agent_config.append(f\"dir '{dir_path}'\")\n if additionalBuildArgs:\n agent_config.append(f\"additionalBuildArgs '{additionalBuildArgs}'\")\n\n config_str = '\\n '.join(agent_config)\n return f\"\"\" agent {{\n dockerfile {{\n {config_str}\n }}\n }}\"\"\"\n elif agent_type == 'kubernetes':\n yaml_content = kwargs.get('yaml', '')\n inheritFrom = kwargs.get('inheritFrom', '')\n\n if inheritFrom:\n return f\"\"\" agent {{\n kubernetes {{\n inheritFrom '{inheritFrom}'\n yaml '''\n{yaml_content}\n'''\n }}\n }}\"\"\"\n else:\n return f\"\"\" agent {{\n kubernetes {{\n yaml '''\n{yaml_content}\n'''\n }}\n }}\"\"\"\n else:\n return \" agent any\"\n\n @staticmethod\n def environment_block(env_vars: Dict[str, str], credentials: Dict[str, str] = None) -> str:\n \"\"\"Generate environment block\"\"\"\n if not env_vars and not credentials:\n return \"\"\n\n lines = [\" environment {\"]\n\n # Regular environment variables\n for key, value in env_vars.items():\n lines.append(f\" {key} = '{value}'\")\n\n # Credential bindings\n if credentials:\n for key, cred_id in credentials.items():\n lines.append(f\" {key} = credentials('{cred_id}')\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n @staticmethod\n def parameters_block(parameters: List[Dict]) -> str:\n \"\"\"Generate parameters block\"\"\"\n if not parameters:\n return \"\"\n\n lines = [\" parameters {\"]\n\n for param in parameters:\n param_type = param.get('type', 'string')\n name = param.get('name', 'PARAM')\n default_value = param.get('defaultValue', '')\n description = param.get('description', '')\n\n if param_type == 'string':\n lines.append(f\" string(name: '{name}', defaultValue: '{default_value}', description: '{description}')\")\n elif param_type == 'text':\n lines.append(f\" text(name: '{name}', defaultValue: '{default_value}', description: '{description}')\")\n elif param_type == 'boolean':\n default_bool = 'true' if default_value else 'false'\n lines.append(f\" booleanParam(name: '{name}', defaultValue: {default_bool}, description: '{description}')\")\n elif param_type == 'choice':\n choices = param.get('choices', [])\n choices_str = ', '.join([f\"'{c}'\" for c in choices])\n lines.append(f\" choice(name: '{name}', choices: [{choices_str}], description: '{description}')\")\n elif param_type == 'password':\n lines.append(f\" password(name: '{name}', defaultValue: '{default_value}', description: '{description}')\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n @staticmethod\n def options_block(options: Dict[str, any]) -> str:\n \"\"\"Generate options block\"\"\"\n if not options:\n return \"\"\n\n lines = [\" options {\"]\n\n if options.get('buildDiscarder'):\n num_to_keep = options['buildDiscarder'].get('numToKeepStr', '10')\n lines.append(f\" buildDiscarder(logRotator(numToKeepStr: '{num_to_keep}'))\")\n\n if options.get('disableConcurrentBuilds'):\n lines.append(\" disableConcurrentBuilds()\")\n\n if options.get('timeout'):\n time = options['timeout'].get('time', 1)\n unit = options['timeout'].get('unit', 'HOURS')\n lines.append(f\" timeout(time: {time}, unit: '{unit}')\")\n\n if options.get('retry'):\n count = options['retry']\n lines.append(f\" retry({count})\")\n\n if options.get('timestamps'):\n lines.append(\" timestamps()\")\n\n if options.get('parallelsAlwaysFailFast'):\n lines.append(\" parallelsAlwaysFailFast()\")\n\n # New advanced options\n if options.get('preserveStashes'):\n build_count = options['preserveStashes'].get('buildCount', 5)\n lines.append(f\" preserveStashes(buildCount: {build_count})\")\n\n if options.get('durabilityHint'):\n hint = options['durabilityHint']\n lines.append(f\" durabilityHint('{hint}')\")\n\n if options.get('quietPeriod'):\n period = options['quietPeriod']\n lines.append(f\" quietPeriod({period})\")\n\n if options.get('skipStagesAfterUnstable'):\n lines.append(\" skipStagesAfterUnstable()\")\n\n if options.get('disableResume'):\n lines.append(\" disableResume()\")\n\n if options.get('checkoutToSubdirectory'):\n subdir = options['checkoutToSubdirectory']\n lines.append(f\" checkoutToSubdirectory('{subdir}')\")\n\n if options.get('newContainerPerStage'):\n lines.append(\" newContainerPerStage()\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n @staticmethod\n def triggers_block(triggers: Dict[str, str]) -> str:\n \"\"\"Generate triggers block\"\"\"\n if not triggers:\n return \"\"\n\n lines = [\" triggers {\"]\n\n if triggers.get('cron'):\n lines.append(f\" cron('{triggers['cron']}')\")\n\n if triggers.get('pollSCM'):\n lines.append(f\" pollSCM('{triggers['pollSCM']}')\")\n\n if triggers.get('upstream'):\n lines.append(f\" upstream(upstreamProjects: '{triggers['upstream']}', threshold: hudson.model.Result.SUCCESS)\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n @staticmethod\n def tools_block(tools: Dict[str, str]) -> str:\n \"\"\"Generate tools block\"\"\"\n if not tools:\n return \"\"\n\n lines = [\" tools {\"]\n\n for tool_type, tool_name in tools.items():\n lines.append(f\" {tool_type} '{tool_name}'\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n @staticmethod\n def when_block(conditions: Dict) -> str:\n \"\"\"Generate when block for conditional stage execution\"\"\"\n if not conditions:\n return \"\"\n\n lines = [\" when {\"]\n\n if conditions.get('branch'):\n lines.append(f\" branch '{conditions['branch']}'\")\n\n if conditions.get('environment'):\n env_name = conditions['environment'].get('name')\n env_value = conditions['environment'].get('value')\n lines.append(f\" environment name: '{env_name}', value: '{env_value}'\")\n\n if conditions.get('expression'):\n lines.append(f\" expression {{ {conditions['expression']} }}\")\n\n if conditions.get('tag'):\n lines.append(f\" tag '{conditions['tag']}'\")\n\n if conditions.get('not'):\n lines.append(\" not {\")\n for subcondition in conditions['not']:\n lines.append(f\" {subcondition}\")\n lines.append(\" }\")\n\n if conditions.get('allOf'):\n lines.append(\" allOf {\")\n for subcondition in conditions['allOf']:\n lines.append(f\" {subcondition}\")\n lines.append(\" }\")\n\n if conditions.get('anyOf'):\n lines.append(\" anyOf {\")\n for subcondition in conditions['anyOf']:\n lines.append(f\" {subcondition}\")\n lines.append(\" }\")\n\n lines.append(\" }\")\n return '\\n'.join(lines)\n\n\nclass ScriptedSyntax:\n \"\"\"Helper functions specific to Scripted Pipeline syntax\"\"\"\n\n @staticmethod\n def node_block(label: Optional[str] = None, content: str = \"\") -> str:\n \"\"\"Generate node block\"\"\"\n if label:\n return f\"\"\"node('{label}') {{\n{content}\n}}\"\"\"\n else:\n return f\"\"\"node {{\n{content}\n}}\"\"\"\n\n @staticmethod\n def stage_block(name: str, content: str) -> str:\n \"\"\"Generate stage block\"\"\"\n return f\"\"\" stage('{name}') {{\n{content}\n }}\"\"\"\n\n @staticmethod\n def try_catch_finally(try_content: str, catch_content: str = \"\", finally_content: str = \"\") -> str:\n \"\"\"Generate try-catch-finally block\"\"\"\n blocks = [f\"\"\" try {{\n{try_content}\n }}\"\"\"]\n\n if catch_content:\n blocks.append(f\"\"\" catch (Exception e) {{\n{catch_content}\n }}\"\"\")\n\n if finally_content:\n blocks.append(f\"\"\" finally {{\n{finally_content}\n }}\"\"\")\n\n return '\\n'.join(blocks)\n\n @staticmethod\n def parallel_block(parallel_stages: Dict[str, str]) -> str:\n \"\"\"Generate parallel block\"\"\"\n stage_blocks = []\n for stage_name, stage_content in parallel_stages.items():\n stage_blocks.append(f\"\"\" '{stage_name}': {{\n{stage_content}\n }}\"\"\")\n\n return f\"\"\" parallel(\n{','.join(stage_blocks)}\n )\"\"\"\n\n @staticmethod\n def withEnv_block(env_vars: List[str], content: str) -> str:\n \"\"\"Generate withEnv block\"\"\"\n env_list = ', '.join([f\"'{var}'\" for var in env_vars])\n return f\"\"\" withEnv([{env_list}]) {{\n{content}\n }}\"\"\"\n\n @staticmethod\n def docker_inside_block(image: str, content: str, args: str = \"\") -> str:\n \"\"\"Generate docker.image().inside block\"\"\"\n if args:\n return f\"\"\" docker.image('{image}').inside('{args}') {{\n{content}\n }}\"\"\"\n else:\n return f\"\"\" docker.image('{image}').inside {{\n{content}\n }}\"\"\"\n\n\nclass ValidationHelpers:\n \"\"\"Helper functions for validation\"\"\"\n\n @staticmethod\n def validate_stage_name(name: str) -> bool:\n \"\"\"Validate stage name\"\"\"\n # Stage names should not be empty and should not contain special characters\n if not name or not name.strip():\n return False\n # Allow alphanumeric, spaces, hyphens, underscores\n return bool(re.match(r'^[a-zA-Z0-9\\s\\-_]+

Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…

, name))\n\n @staticmethod\n def normalize_stage_name(name: str, fallback: str = 'Custom Stage') -> str:\n \"\"\"Normalize and validate a stage display name.\"\"\"\n value = '' if name is None else str(name)\n normalized = re.sub(r'[^a-zA-Z0-9\\s\\-_]', ' ', value).strip()\n normalized = re.sub(r'\\s+', ' ', normalized)\n\n if not normalized:\n normalized = fallback\n\n if not ValidationHelpers.validate_stage_name(normalized):\n raise ValueError(f\"Invalid stage name after normalization: {name!r}\")\n\n return normalized\n\n @staticmethod\n def validate_agent_type(agent_type: str) -> bool:\n \"\"\"Validate agent type\"\"\"\n valid_types = ['any', 'none', 'label', 'docker', 'dockerfile', 'kubernetes']\n return agent_type.lower() in valid_types\n\n @staticmethod\n def validate_cron_expression(expression: str) -> bool:\n \"\"\"Basic validation of cron expression\"\"\"\n # Should have 5 parts: MINUTE HOUR DOM MONTH DOW\n parts = expression.split()\n return len(parts) == 5\n\n @staticmethod\n def sanitize_pipeline_name(name: str) -> str:\n \"\"\"Sanitize pipeline/job name for use in filenames\"\"\"\n # Replace special characters with hyphens\n sanitized = re.sub(r'[^a-zA-Z0-9\\-_]', '-', name)\n # Remove multiple consecutive hyphens\n sanitized = re.sub(r'-+', '-', sanitized)\n # Remove leading/trailing hyphens\n sanitized = sanitized.strip('-')\n return sanitized.lower()\n\n @staticmethod\n def parse_stage_list(stages_value: str) -> list:\n \"\"\"Parse comma-separated stage values into normalized lowercase keys.\n\n Keys are lowercased automatically. Raises ValueError if any key contains\n characters outside [a-z0-9_-] or if the resulting list is empty.\n \"\"\"\n _STAGE_KEY_PATTERN = re.compile(r'^[a-z0-9][a-z0-9_-]*

Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…

)\n stages = []\n for raw_stage in stages_value.split(','):\n stage = raw_stage.strip().lower()\n if not stage:\n continue\n if not _STAGE_KEY_PATTERN.fullmatch(stage):\n raise ValueError(\n f\"Invalid stage key '{raw_stage.strip()}'. \"\n \"Use only lowercase letters, numbers, '-' and '_'\"\n )\n stages.append(stage)\n if not stages:\n raise ValueError('At least one stage must be provided via --stages')\n return stages\n\n\nclass FormattingHelpers:\n \"\"\"Helper functions for formatting output\"\"\"\n\n @staticmethod\n def add_header_comment(content: str, description: str = \"Generated Jenkinsfile\") -> str:\n \"\"\"Add header comment to Jenkinsfile\"\"\"\n header = f\"\"\"// {description}\n// Generated by Jenkinsfile Generator\n// Date: {FormattingHelpers.get_timestamp()}\n\n\"\"\"\n return header + content\n\n @staticmethod\n def get_timestamp() -> str:\n \"\"\"Get current timestamp\"\"\"\n from datetime import datetime\n return datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n\n @staticmethod\n def indent_block(content: str, levels: int = 1) -> str:\n \"\"\"Indent a block of text\"\"\"\n indent = ' ' * levels\n lines = content.split('\\n')\n return '\\n'.join([f\"{indent}{line}\" if line.strip() else '' for line in lines])\n\n @staticmethod\n def format_jenkinsfile(content: str) -> str:\n \"\"\"Format Jenkinsfile with proper indentation and spacing\"\"\"\n # Remove excessive blank lines (3+ newlines -> 2 newlines)\n content = re.sub(r'\\n{3,}', '\\n\\n', content)\n # Remove blank lines after opening braces\n content = re.sub(r'{\\n\\n(\\s*)', r'{\\n\\1', content)\n # Remove blank lines before closing braces\n content = re.sub(r'\\n\\n(\\s*})', r'\\n\\1', content)\n # Ensure proper spacing around blocks\n content = re.sub(r'}\\n([a-zA-Z])', r'}\\n\\n\\1', content)\n return content.strip() + '\\n'\n","content_type":"text/x-python; charset=utf-8","language":"python","size":18336,"content_sha256":"e23364a39870157b4cb7296590a923000debc82bd48ba98b6b23ae0738ecaecf"},{"filename":"scripts/test_generate_declarative.py","content":"#!/usr/bin/env python3\n\"\"\"Regression tests for generate_declarative.py behavior.\"\"\"\n\nfrom pathlib import Path\nimport sys\nimport tempfile\nimport unittest\n\n\nSCRIPT_DIR = Path(__file__).resolve().parent\nsys.path.insert(0, str(SCRIPT_DIR))\nsys.path.insert(0, str(SCRIPT_DIR / \"lib\"))\n\nfrom generate_declarative import DeclarativePipelineGenerator, resolve_k8s_yaml # noqa: E402\nfrom syntax_helpers import ValidationHelpers # noqa: E402\n\n\nclass ResolveK8sYamlRegressionTests(unittest.TestCase):\n \"\"\"Cover path-vs-inline resolution behavior for --k8s-yaml.\"\"\"\n\n def test_reads_existing_file_without_extension(self):\n with tempfile.TemporaryDirectory() as temp_dir:\n yaml_path = Path(temp_dir) / \"pod-template\"\n yaml_content = \"apiVersion: v1\\nkind: Pod\\nmetadata:\\n name: demo\\n\"\n yaml_path.write_text(yaml_content, encoding=\"utf-8\")\n\n self.assertEqual(resolve_k8s_yaml(str(yaml_path)), yaml_content)\n\n def test_missing_non_inline_value_raises_error(self):\n with tempfile.TemporaryDirectory() as temp_dir:\n missing_path = Path(temp_dir) / \"does-not-exist\"\n with self.assertRaisesRegex(\n ValueError,\n \"--k8s-yaml must be inline YAML content or an existing file path\",\n ):\n resolve_k8s_yaml(str(missing_path))\n\n def test_missing_single_token_value_raises_error(self):\n with self.assertRaisesRegex(\n ValueError,\n \"--k8s-yaml must be inline YAML content or an existing file path\",\n ):\n resolve_k8s_yaml(\"definitely-missing-k8s-yaml-token\")\n\n def test_inline_yaml_is_preserved(self):\n inline_yaml = \"apiVersion: v1\\nkind: Pod\\nmetadata:\\n name: inline\"\n self.assertEqual(resolve_k8s_yaml(inline_yaml), inline_yaml)\n\n\nclass StageListParsingRegressionTests(unittest.TestCase):\n \"\"\"Lock stage key normalization and validation rules.\"\"\"\n\n def test_parse_stage_list_normalizes_case_and_spacing(self):\n stages = ValidationHelpers.parse_stage_list(\" Build,TEST,parallel-tests,custom_stage \")\n self.assertEqual(stages, [\"build\", \"test\", \"parallel-tests\", \"custom_stage\"])\n\n def test_parse_stage_list_rejects_invalid_keys(self):\n with self.assertRaisesRegex(ValueError, \"Invalid stage key\"):\n ValidationHelpers.parse_stage_list(\"build,Security Scan\")\n\n def test_parse_stage_list_requires_at_least_one_stage(self):\n with self.assertRaisesRegex(ValueError, \"At least one stage must be provided\"):\n ValidationHelpers.parse_stage_list(\" , , \")\n\n\nclass ParallelFailFastRegressionTests(unittest.TestCase):\n \"\"\"Verify parallel fail-fast rendering combinations.\"\"\"\n\n @staticmethod\n def _render_pipeline(options=None, parallel_fail_fast=True):\n config = {\n \"name\": \"Parallel Fail Fast Regression\",\n \"stages\": [\"parallel-tests\"],\n \"parallel_fail_fast\": parallel_fail_fast,\n }\n if options is not None:\n config[\"options\"] = options\n return DeclarativePipelineGenerator(config).generate()\n\n def test_parallel_stage_includes_fail_fast_by_default(self):\n output = self._render_pipeline()\n self.assertIn(\"failFast true\", output)\n\n def test_global_fail_fast_option_omits_redundant_stage_flag(self):\n output = self._render_pipeline(options={\"parallelsAlwaysFailFast\": True})\n self.assertIn(\"parallelsAlwaysFailFast()\", output)\n self.assertNotIn(\"failFast true\", output)\n\n def test_parallel_stage_can_disable_fail_fast(self):\n output = self._render_pipeline(parallel_fail_fast=False)\n self.assertNotIn(\"failFast true\", output)\n self.assertNotIn(\"parallelsAlwaysFailFast()\", output)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3816,"content_sha256":"f7aa6d09dcc247228b7505e39e3dd270983ef7ec059c81aebc545c8a3f6f7879"},{"filename":"scripts/test_generate_shared_library.py","content":"#!/usr/bin/env python3\n\"\"\"Regression tests for generate_shared_library.py.\"\"\"\n\nfrom pathlib import Path\nimport sys\nimport tempfile\nimport unittest\n\n\nSCRIPT_DIR = Path(__file__).resolve().parent\nsys.path.insert(0, str(SCRIPT_DIR))\n\nfrom generate_shared_library import SharedLibraryGenerator # noqa: E402\n\n\nclass SharedLibraryDeployTemplateRegressionTests(unittest.TestCase):\n \"\"\"Ensure generated deploy helper remains Kubernetes-safe.\"\"\"\n\n def _render_deploy_template(self) -> str:\n with tempfile.TemporaryDirectory() as temp_dir:\n generator = SharedLibraryGenerator(\n name=\"example-lib\",\n package=\"org.example\",\n output_dir=temp_dir,\n )\n generator.generate()\n deploy_path = Path(temp_dir) / \"example-lib\" / \"vars\" / \"deployApp.groovy\"\n return deploy_path.read_text(encoding=\"utf-8\")\n\n def test_deployment_name_uses_job_base_name_and_dns_sanitization(self):\n text = self._render_deploy_template()\n\n self.assertIn(\"config.get('deploymentName', env.JOB_BASE_NAME ?: '')\", text)\n self.assertNotIn(\"config.get('deploymentName', env.JOB_NAME)\", text)\n self.assertIn(\".replaceAll(/[^a-z0-9-]/, '-')\", text)\n self.assertIn(\".replaceAll(/-+/, '-')\", text)\n self.assertIn(\".replaceAll(/^-|-$/, '')\", text)\n self.assertIn(\"if (deploymentName.length() > 63)\", text)\n self.assertIn(\n \"deploymentName is required and must resolve to a valid Kubernetes deployment name\",\n text,\n )\n\n def test_rollout_command_uses_quoted_env_variables(self):\n text = self._render_deploy_template()\n\n self.assertIn(\"withEnv([\", text)\n self.assertIn(\"\\\"KUBE_NAMESPACE=${namespace}\\\"\", text)\n self.assertIn(\"\\\"DEPLOYMENT_NAME=${deploymentName}\\\"\", text)\n self.assertIn(\"\\\"MANIFESTS_PATH=${manifests}\\\"\", text)\n self.assertIn(\"set -euo pipefail\", text)\n self.assertIn(\"kubectl apply -f \\\"$MANIFESTS_PATH\\\" -n \\\"$KUBE_NAMESPACE\\\"\", text)\n self.assertIn(\n \"kubectl rollout status \\\"deployment/$DEPLOYMENT_NAME\\\" -n \\\"$KUBE_NAMESPACE\\\" --timeout=300s\",\n text,\n )\n self.assertNotIn(\"deployment/${deploymentName}\", text)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2325,"content_sha256":"01f4bfbcaf90a59676a511fbc34eb1d857e1770c336c1ae0347cd3b5b0f60a3b"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Jenkinsfile Generator Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Trigger Phrases","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Generate a CI pipeline for Maven/Gradle/npm\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create a Jenkins deployment pipeline with approvals\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Build a Jenkinsfile with parallel test stages\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create a scripted pipeline with dynamic stage logic\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Scaffold a Jenkins shared library\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Generate a Jenkinsfile for Docker or Kubernetes agents\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creating new Jenkinsfiles (declarative or scripted)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI/CD pipelines, Docker/Kubernetes deployments","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parallel execution, matrix builds, parameterized pipelines","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DevSecOps pipelines with security scanning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shared library scaffolding","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Declarative vs Scripted Decision Tree","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Choose ","type":"text"},{"text":"Declarative","type":"text","marks":[{"type":"strong"}]},{"text":" by default when stage order and behavior are mostly static.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Choose ","type":"text"},{"text":"Scripted","type":"text","marks":[{"type":"strong"}]},{"text":" when runtime-generated stages, complex loops, or dynamic control flow are required.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Choose ","type":"text"},{"text":"Shared Library scaffolding","type":"text","marks":[{"type":"strong"}]},{"text":" when request is about reusable pipeline functions (","type":"text"},{"text":"vars/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"src/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"resources/","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If unsure, start Declarative and only switch to Scripted if requirements cannot be expressed cleanly.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Template Map","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Path","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use When","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Declarative basic","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/templates/declarative/basic.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Standard CI/CD with predictable stages","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Declarative parallel example","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"examples/declarative-parallel.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Parallel test/build branches with fail-fast behavior","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Declarative kubernetes example","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"examples/declarative-kubernetes.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kubernetes agent execution using pod templates","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scripted basic","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/templates/scripted/basic.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Complex conditional logic or generated stages","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shared library scaffold","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generated by ","type":"text"},{"text":"scripts/generate_shared_library.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reusable pipeline functions and organization-wide patterns","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"// Minimal Declarative Pipeline\npipeline {\n agent any\n stages {\n stage('Build') { steps { sh 'make' } }\n stage('Test') { steps { sh 'make test' } }\n }\n}\n\n// Error-tolerant stage\nstage('Flaky Tests') {\n steps {\n catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {\n sh 'run-flaky-tests.sh'\n }\n }\n}\n\n// Conditional deployment with approval\nstage('Deploy') {\n when { branch 'main'; beforeAgent true }\n input { message 'Deploy to production?' }\n steps { sh './deploy.sh' }\n}","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Option","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"timeout(time: 1, unit: 'HOURS')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevent hung builds","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"buildDiscarder(logRotator(numToKeepStr: '10'))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Manage disk space","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"disableConcurrentBuilds()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevent race conditions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Continue on error","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Capabilities","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Declarative Pipelines (RECOMMENDED)","type":"text"}]},{"type":"paragraph","content":[{"text":"Process:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read templates for structure reference:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"assets/templates/declarative/basic.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]},{"text":" to understand the standard structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Templates show the expected sections: pipeline → agent → environment → options → parameters → stages → post","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For complex requests, adapt the structure rather than copying verbatim","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consult reference documentation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for performance, security, and reliability patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"references/common_plugins.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for plugin-specific syntax","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate with required elements:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Proper stages with descriptive names","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Environment block with credentials binding (never hardcode secrets)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Options: timeout, buildDiscarder, timestamps, disableConcurrentBuilds","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Post conditions: always (cleanup), success (artifacts), failure (notifications)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always add ","type":"text","marks":[{"type":"strong"}]},{"text":"failFast true","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" or ","type":"text","marks":[{"type":"strong"}]},{"text":"parallelsAlwaysFailFast()","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" for parallel blocks","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always include ","type":"text","marks":[{"type":"strong"}]},{"text":"fingerprint: true","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" when using ","type":"text","marks":[{"type":"strong"}]},{"text":"archiveArtifacts","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ALWAYS validate","type":"text","marks":[{"type":"strong"}]},{"text":" using devops-skills:jenkinsfile-validator skill","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Scripted Pipelines","type":"text"}]},{"type":"paragraph","content":[{"text":"When:","type":"text","marks":[{"type":"strong"}]},{"text":" Complex conditional logic, dynamic generation, full Groovy control ","type":"text"},{"text":"Process:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read templates for structure reference:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"assets/templates/scripted/basic.Jenkinsfile","type":"text","marks":[{"type":"code_inline"}]},{"text":" for node/stage patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understand try-catch-finally structure for error handling","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement try-catch-finally for error handling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ALWAYS validate","type":"text","marks":[{"type":"strong"}]},{"text":" using devops-skills:jenkinsfile-validator skill","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Parallel/Matrix Pipelines","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"parallel {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" block or ","type":"text"},{"text":"matrix {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"axes {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" for multi-dimensional builds.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Default behavior is fail-fast for generated parallel pipelines (","type":"text"},{"text":"parallelsAlwaysFailFast()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or stage-level ","type":"text"},{"text":"failFast true","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Security Scanning (DevSecOps)","type":"text"}]},{"type":"paragraph","content":[{"text":"Add SonarQube, OWASP Dependency-Check, Trivy stages with fail thresholds.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Shared Library Scaffolding","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/generate_shared_library.py --name my-library --package org.example","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Declarative Syntax Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Agent Types","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"agent any // Any available agent\nagent { label 'linux && docker' } // Label-based\nagent { docker { image 'maven:3.9.11-eclipse-temurin-21' } }\nagent { kubernetes { yaml '...' } } // K8s pod template\nagent { kubernetes { yamlFile 'pod.yaml' } } // External YAML","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Environment & Credentials","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"environment {\n VERSION = '1.0.0'\n AWS_KEY = credentials('aws-key-id') // Creates _USR and _PSW vars\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Options","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"options {\n buildDiscarder(logRotator(numToKeepStr: '10'))\n timeout(time: 1, unit: 'HOURS')\n disableConcurrentBuilds()\n timestamps()\n parallelsAlwaysFailFast()\n durabilityHint('PERFORMANCE_OPTIMIZED') // 2-6x faster for simple pipelines\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Parameters","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"parameters {\n string(name: 'VERSION', defaultValue: '1.0.0')\n choice(name: 'ENV', choices: ['dev', 'staging', 'prod'])\n booleanParam(name: 'SKIP_TESTS', defaultValue: false)\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"When Conditions","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Example","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"branch","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"branch 'main'","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"branch pattern: 'release/*', comparator: 'GLOB'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tag","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tag pattern: 'v*', comparator: 'GLOB'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"changeRequest","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"changeRequest target: 'main'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"changeset","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"changeset 'src/**/*.java'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"expression","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"expression { env.DEPLOY == 'true' }","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"allOf/anyOf/not","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Combine conditions","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"beforeAgent true","type":"text","marks":[{"type":"code_inline"}]},{"text":" to skip agent allocation if condition fails.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Error Handling","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { sh '...' }\nwarnError('msg') { sh '...' } // Mark UNSTABLE but continue\nunstable(message: 'Coverage low') // Explicit UNSTABLE\nerror('Config missing') // Fail without stack trace","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Post Section","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"post {\n always { junit '**/target/*.xml'; cleanWs() }\n success { archiveArtifacts artifacts: '**/*.jar', fingerprint: true }\n failure { slackSend color: 'danger', message: 'Build failed' }\n fixed { echo 'Build fixed!' }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Order:","type":"text","marks":[{"type":"strong"}]},{"text":" always → changed → fixed → regression → failure → success → unstable → cleanup","type":"text"}]},{"type":"paragraph","content":[{"text":"NOTE:","type":"text","marks":[{"type":"strong"}]},{"text":" Always use ","type":"text"},{"text":"fingerprint: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"archiveArtifacts","type":"text","marks":[{"type":"code_inline"}]},{"text":" for build traceability and artifact tracking.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Parallel & Matrix","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" Always ensure parallel blocks fail fast on first failure using one of these approaches:","type":"text"}]},{"type":"paragraph","content":[{"text":"Option 1: Global (RECOMMENDED)","type":"text","marks":[{"type":"strong"}]},{"text":" - Use ","type":"text"},{"text":"parallelsAlwaysFailFast()","type":"text","marks":[{"type":"code_inline"}]},{"text":" in pipeline options:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"options {\n parallelsAlwaysFailFast() // Applies to ALL parallel blocks in pipeline\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"This is the preferred approach as it covers all parallel blocks automatically.","type":"text"}]},{"type":"paragraph","content":[{"text":"Option 2: Per-block","type":"text","marks":[{"type":"strong"}]},{"text":" - Use ","type":"text"},{"text":"failFast true","type":"text","marks":[{"type":"code_inline"}]},{"text":" on individual parallel stages:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"stage('Tests') {\n failFast true // Only affects this parallel block\n parallel {\n stage('Unit') { steps { sh 'npm test:unit' } }\n stage('E2E') { steps { sh 'npm test:e2e' } }\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"NOTE:","type":"text","marks":[{"type":"strong"}]},{"text":" When ","type":"text"},{"text":"parallelsAlwaysFailFast()","type":"text","marks":[{"type":"code_inline"}]},{"text":" is set in options, explicit ","type":"text"},{"text":"failFast true","type":"text","marks":[{"type":"code_inline"}]},{"text":" on individual parallel blocks is redundant.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"stage('Matrix') {\n failFast true\n matrix {\n axes {\n axis { name 'PLATFORM'; values 'linux', 'windows' }\n axis { name 'BROWSER'; values 'chrome', 'firefox' }\n }\n excludes { exclude { axis { name 'PLATFORM'; values 'linux' }; axis { name 'BROWSER'; values 'safari' } } }\n stages { stage('Test') { steps { echo \"Testing ${PLATFORM}/${BROWSER}\" } } }\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Input (Manual Approval)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"stage('Deploy') {\n input { message 'Deploy?'; ok 'Deploy'; submitter 'admin,ops' }\n steps { sh './deploy.sh' }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" Place ","type":"text"},{"text":"input","type":"text","marks":[{"type":"code_inline"}]},{"text":" outside steps to avoid holding agents.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripted Syntax Reference","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"node('agent-label') {\n try {\n stage('Build') { sh 'make build' }\n stage('Test') { sh 'make test' }\n } catch (Exception e) {\n currentBuild.result = 'FAILURE'\n throw e\n } finally {\n deleteDir()\n }\n}\n\n// Parallel\nparallel(\n 'Unit': { node { sh 'npm test:unit' } },\n 'E2E': { node { sh 'npm test:e2e' } }\n)\n\n// Environment\nwithEnv(['VERSION=1.0.0']) { sh 'echo $VERSION' }\nwithCredentials([string(credentialsId: 'key', variable: 'KEY')]) { sh 'curl -H \"Auth: $KEY\" ...' }","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"@NonCPS for Non-Serializable Operations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"@NonCPS\ndef parseJson(String json) {\n new groovy.json.JsonSlurper().parseText(json)\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Rules:","type":"text","marks":[{"type":"strong"}]},{"text":" No pipeline steps (","type":"text"},{"text":"sh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"echo","type":"text","marks":[{"type":"code_inline"}]},{"text":") inside @NonCPS. Use for JsonSlurper, iterators, regex Matchers.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Docker & Kubernetes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Docker Agent","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"agent { docker { image 'maven:3.9.11'; args '-v $HOME/.m2:/root/.m2'; reuseNode true } }","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Build & Push","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"def img = docker.build(\"myapp:${BUILD_NUMBER}\")\ndocker.withRegistry('https://registry.example.com', 'creds') { img.push(); img.push('latest') }","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Kubernetes Pod","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"agent {\n kubernetes {\n yaml '''\napiVersion: v1\nkind: Pod\nspec:\n containers:\n - name: maven\n image: maven:3.9.11-eclipse-temurin-21\n command: [sleep, 99d]\n'''\n }\n}\n// Use: container('maven') { sh 'mvn package' }","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Shared Libraries","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"@Library('my-shared-library') _\n// or dynamically: library '[email protected]'\n\n// vars/log.groovy\ndef info(msg) { echo \"INFO: ${msg}\" }\n\n// Usage\nlog.info 'Starting build'","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Validation Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"CRITICAL: ALWAYS validate using devops-skills:jenkinsfile-validator skill:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate Jenkinsfile","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Invoke ","type":"text"},{"text":"devops-skills:jenkinsfile-validator","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handle validation results by severity:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ERRORS:","type":"text","marks":[{"type":"strong"}]},{"text":" MUST fix before presenting to user - these break the pipeline","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"WARNINGS:","type":"text","marks":[{"type":"strong"}]},{"text":" SHOULD fix - these indicate potential issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"INFO/SUGGESTIONS:","type":"text","marks":[{"type":"strong"}]},{"text":" Consider applying based on use case:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"failFast true","type":"text","marks":[{"type":"code_inline"}]},{"text":" for parallel blocks → apply by default","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build triggers → ask user if they want automated builds","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Other optimizations → apply if they improve the pipeline","type":"text"}]}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Re-validate after fixes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Only present validated Jenkinsfiles to user","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Validation commands:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Full validation (syntax + security + best practices)\nbash ../jenkinsfile-validator/scripts/validate_jenkinsfile.sh Jenkinsfile\n\n# Syntax only (fastest)\nbash ../jenkinsfile-validator/scripts/validate_jenkinsfile.sh --syntax-only Jenkinsfile","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Generator Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use scripts vs manual generation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use scripts for:","type":"text","marks":[{"type":"strong"}]},{"text":" Simple, standard pipelines with common patterns (basic CI, straightforward CD)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use manual generation for:","type":"text","marks":[{"type":"strong"}]},{"text":" Complex pipelines with multiple features (parallel tests + security scanning + Docker + K8s deployments), custom logic, or non-standard requirements","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Script Arguments: Required vs Optional","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"generate_declarative.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Required: ","type":"text"},{"text":"--output","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional: ","type":"text"},{"text":"--stages","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--agent","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--build-tool","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--build-cmd","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--test-cmd","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--deploy-*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--notification-*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--archive-artifacts","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--k8s-yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Notes:","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--k8s-yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" accepts either inline YAML content or a path to an existing ","type":"text"},{"text":".yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" file.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stage keys are validated (","type":"text"},{"text":"[a-z0-9_-]","type":"text","marks":[{"type":"code_inline"}]},{"text":") and shell commands are emitted as escaped Groovy literals.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"generate_scripted.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Required: ","type":"text"},{"text":"--output","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional: stage/agent/SCM/notification parameters depending on requested pipeline features.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"generate_shared_library.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Required: ","type":"text"},{"text":"--name","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional: ","type":"text"},{"text":"--package","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--output","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shared library deployment helper now includes explicit rollout target (","type":"text"},{"text":"deployment/\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":") and notification helper emits valid HTML email bodies.","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Declarative (simple pipelines)\npython3 scripts/generate_declarative.py --output Jenkinsfile --stages build,test,deploy --agent docker\n\n# Scripted (simple pipelines)\npython3 scripts/generate_scripted.py --output Jenkinsfile --stages build,test --agent label:linux\n\n# Shared Library (always use script for scaffolding)\npython3 scripts/generate_shared_library.py --name my-library --package com.example","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Done Criteria","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pipeline style selection (Declarative vs Scripted) is explicit and justified.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generated Jenkinsfiles pass smoke validation with executable validator commands.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parallel pipelines are fail-fast by default unless user explicitly requests otherwise.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom stage names and shell commands are safely emitted (no unescaped Groovy literals).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--k8s-yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" works with both inline YAML and existing file paths.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Notification-enabled post blocks still archive artifacts when requested.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Plugin Documentation Lookup","type":"text"}]},{"type":"paragraph","content":[{"text":"Always consult Context7 or WebSearch for:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plugins NOT covered in ","type":"text"},{"text":"references/common_plugins.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Version-specific documentation requests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complex plugin configurations or advanced options","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When user explicitly asks for latest documentation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"May skip external lookup when:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Using basic plugin syntax already documented in ","type":"text"},{"text":"references/common_plugins.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simple, well-documented plugin steps (e.g., basic ","type":"text"},{"text":"sh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"checkout scm","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"junit","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Plugins covered in common_plugins.md:","type":"text","marks":[{"type":"strong"}]},{"text":" Git, Docker, Kubernetes, Credentials, JUnit, Slack, SonarQube, OWASP Dependency-Check, Email, AWS, Azure, HTTP Request, Microsoft Teams, Nexus, Artifactory, GitHub","type":"text"}]},{"type":"paragraph","content":[{"text":"Lookup methods (in order of preference):","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Context7:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"mcp__context7__resolve-library-id","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"/jenkinsci/\u003cplugin-name>-plugin","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"WebSearch:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"Jenkins [plugin-name] plugin documentation 2025","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Official:","type":"text","marks":[{"type":"strong"}]},{"text":" plugins.jenkins.io, jenkins.io/doc/pipeline/steps/","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Performance, security, reliability patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/common_plugins.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Git, Docker, K8s, credentials, notifications","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"assets/templates/","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Declarative and scripted templates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"devops-skills:jenkinsfile-validator","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill - Syntax and best practices validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Always prefer Declarative unless scripted flexibility is required.","type":"text","marks":[{"type":"strong"}]}]}]},"metadata":{"date":"2026-11-02","name":"jenkinsfile-generator","author":"@skillopedia","source":{"stars":331,"repo_name":"cc-devops-skills","origin_url":"https://github.com/akin-ozer/cc-devops-skills/blob/HEAD/devops-skills-plugin/skills/jenkinsfile-generator/SKILL.md","repo_owner":"akin-ozer","body_sha256":"eebcb2680fbb4a7462d676aff066d154782a4139aab02d58bb52c66d2d3fec5c","cluster_key":"547b9078c53b43f95b6ab7e461ca68163130918c5c09796a360c5cd5cc9cdb67","clean_bundle":{"format":"clean-skill-bundle-v1","source":"akin-ozer/cc-devops-skills/devops-skills-plugin/skills/jenkinsfile-generator/SKILL.md","text_bytes":131869,"source_commit":"276af751e659315aaf56d3ad13d7c26f4e72e28a","skills_sh_rank":14569,"attachment_count":10,"text_attachments":10,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"devops-skills-plugin/skills/jenkinsfile-generator/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"devops-infrastructure","category_label":"DevOps"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"devops-infrastructure","import_tag":"skills-sh-top20k-2026-11","description":"Generate/create/scaffold Jenkinsfile — declarative, scripted, shared library, CI/CD pipelines."}},"renderedAt":1793686407794}

Jenkinsfile Generator Skill Generate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill. Trigger Phrases - "Generate a CI pipeline for Maven/Gradle/npm" - "Create a Jenkins deployment pipeline with approvals" - "Build a Jenkinsfile with parallel test stages" - "Create a scripted pipeline with dynamic stage logic" - "Scaffold a Jenkins shared library" - "Generate a Jenkinsfile for Docker or Kubernetes agents" When to Use - Creating new Jenkinsfiles (declarative or scripted) - CI/CD pipelines, Docker/Kubernet…