Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/singularity/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class Application {
*/
bool generate_security_report(const LanguageStats& stats);

/**
* @brief Generate workflow templates for the repository
* @param stats Language statistics from analysis
* @return True if workflow generation successful
*/
bool generate_workflow_templates(const LanguageStats& stats);

/**
* @brief Progress callback function
* @param percentage Progress percentage (0-100)
Expand All @@ -85,7 +92,9 @@ class Application {
std::string repo_url_;
bool verbose_{false};
bool generate_report_{false};
bool generate_workflows_{false};
std::string output_file_;
std::string output_dir_;
};

} // namespace singularity
147 changes: 147 additions & 0 deletions include/singularity/workflow_generator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#pragma once

#include "singularity/language_stats.hpp"
#include <nlohmann/json.hpp>
#include <string>
#include <map>
#include <vector>
#include <filesystem>
#include <optional>

namespace singularity {

/**
* @brief Class representing a MegaLinter workflow generator
*/
class WorkflowGenerator {
public:
/**
* @brief Get singleton instance
* @return Singleton instance
*/
static WorkflowGenerator& instance();

/**
* @brief Initialize the workflow generator
* @param security_mappings Path to security mappings JSON file
* @return True if initialization successful
*/
bool initialize(const std::string& security_mappings = "security_mappings.json");

/**
* @brief Generate MegaLinter workflow templates based on detected languages
* @param stats Language statistics from repository analysis
* @param output_dir Directory where workflow files should be written
* @return True if generation successful
*/
bool generate_workflows(const LanguageStats& stats, const std::string& output_dir = "output");

/**
* @brief Check if the workflow generator is initialized
* @return True if initialized
*/
bool is_initialized() const { return initialized_; }

private:
WorkflowGenerator();
WorkflowGenerator(const WorkflowGenerator&) = delete;
WorkflowGenerator& operator=(const WorkflowGenerator&) = delete;

/**
* @brief Generate a GitHub Actions workflow file for MegaLinter
* @param scan_type The type of scan (e.g., "static_analysis")
* @param languages Vector of languages to include in the workflow
* @param output_dir Directory where workflow file should be written
* @return True if generation successful
*/
bool generate_megalinter_workflow(
const std::string& scan_type,
const std::vector<std::string>& languages,
const std::string& output_dir);

/**
* @brief Generate MegaLinter configuration file
* @param languages Vector of languages to include in configuration
* @param output_dir Directory where configuration file should be written
* @return True if generation successful
*/
bool generate_megalinter_config(
const std::vector<std::string>& languages,
const std::string& output_dir);

/**
* @brief Generate a GitHub Actions workflow file for Lizard complexity analysis
* @param languages Vector of languages to analyze
* @param output_dir Directory where workflow file should be written
* @return True if generation successful
*/
bool generate_lizard_workflow(
const std::vector<std::string>& languages,
const std::string& output_dir);

/**
* @brief Map Singularity scan types to MegaLinter linter groups
* @param scan_type The scan type from security_mappings.json
* @return Corresponding MegaLinter linter group
*/
std::string map_to_megalinter_group(const std::string& scan_type);

/**
* @brief Get appropriate MegaLinter linters for a language and scan type
* @param language The language name
* @param scan_type The scan type
* @return Vector of linter names
*/
std::vector<std::string> get_linters_for_language(
const std::string& language,
const std::string& scan_type);

/**
* @brief Get generic MegaLinter linters for a scan type (regardless of language)
* @param scan_type The scan type
* @return Vector of linter names
*/
std::vector<std::string> get_generic_linters_for_scan_type(
const std::string& scan_type);

/**
* @brief Add tool-specific configurations to the workflow file
* @param workflow_file Output stream for the workflow file
* @param language The language name
* @param scan_type The scan type
*/
void add_tool_configs(
std::ofstream& workflow_file,
const std::string& language,
const std::string& scan_type);

nlohmann::json security_mappings_;
bool initialized_ = false;

// Map tool names to MegaLinter linters
const std::unordered_map<std::string, std::string> tool_to_linter = {
{"Cppcheck", "CPP_CPPCHECK"},
{"CppLint", "CPP_CPPLINT"},
{"clang-tidy", "CPP_CLANG_TIDY"},
{"Clang Static Analyzer", "CPP_CLANG_TIDY"},
{"FlawFinder", "CPP_FLAWFINDER"},
{"Bandit", "PYTHON_BANDIT"},
{"Pylint", "PYTHON_PYLINT"},
{"Black", "PYTHON_BLACK"},
{"Safety", "PYTHON_SAFETY"},
{"Semgrep", "REPOSITORY_SEMGREP"},
{"GitLeaks", "REPOSITORY_GITLEAKS"},
{"Secretlint", "REPOSITORY_SECRETLINT"},
{"OWASP Dependency-Check", "REPOSITORY_DEPENDENCY_CHECK"},
{"Dependabot", "REPOSITORY_DEPENDABOT"},
{"Fortify", "REPOSITORY_FORTIFY"},
{"Detect-secrets", "REPOSITORY_SECRETLINT"},
{"OWASP ZAP", "REPOSITORY_ZAP"},
{"licensechecker", "REPOSITORY_LICENSECHECKER"},
{"scancode-toolkit", "REPOSITORY_SCANCODE"},
{"REUSE Tool", "REPOSITORY_REUSE"},
{"Trivy", "REPOSITORY_TRIVY"}
};
};

} // namespace singularity
27 changes: 15 additions & 12 deletions security_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
},
"Cppcheck": {
"supported_languages": ["C", "C++"],
"configuration": "Enable all checks with focus on security-related issues."
"configuration": "Enable all checks with focus on security-related issues.",
"default_for_language": true
},
"Bandit": {
"supported_languages": ["Python"],
"configuration": "Focus on input validation, SQL injection, and command injection vulnerabilities."
"configuration": "Focus on input validation, SQL injection, and command injection vulnerabilities.",
"default_for_language": true
},
"Pylint": {
"supported_languages": ["Python"],
Expand All @@ -36,7 +38,8 @@
},
"Dependabot": {
"supported_languages": ["C", "C++", "Python"],
"configuration": "Configure with security advisories and version updates for all supported package ecosystems."
"configuration": "Configure with security advisories and version updates for all supported package ecosystems.",
"default_for_language": true
}
}
},
Expand All @@ -48,7 +51,8 @@
},
"Semgrep": {
"supported_languages": ["C", "C++", "Python"],
"configuration": "Use language-specific rule sets to detect security issues."
"configuration": "Use language-specific rule sets to detect security issues.",
"default_for_language": true
},
"Fortify": {
"supported_languages": ["C", "C++", "Python"],
Expand All @@ -64,7 +68,8 @@
},
"clang-tidy": {
"supported_languages": ["C", "C++"],
"configuration": "Configure with -checks=bugprone-*,cert-*,clang-analyzer-security.*,performance-*"
"configuration": "Configure with -checks=bugprone-*,cert-*,clang-analyzer-security.*,performance-*",
"default_for_language": true
},
"CppLint": {
"supported_languages": ["C++"],
Expand All @@ -74,18 +79,15 @@
"supported_languages": ["Python"],
"configuration": "Scan for common security issues in Python code."
},
"Pylint security plugins": {
"Pylint": {
"supported_languages": ["Python"],
"configuration": "Enable all security plugins in Pylint."
"configuration": "Enable all security plugins in Pylint.",
"default_for_language": true
}
}
},
"code_quality": {
"tools": {
"Cppcheck": {
"supported_languages": ["C", "C++"],
"configuration": "Enable all checks with focus on code quality issues."
},
"Radon": {
"supported_languages": ["Python"],
"configuration": "Compute code metrics and complexity."
Expand All @@ -96,7 +98,8 @@
},
"Lizard": {
"supported_languages": ["C", "C++", "Python"],
"configuration": "Analyze cyclomatic complexity in code base. Configure to fail builds with high complexity scores."
"configuration": "Analyze cyclomatic complexity in code base. Configure to fail builds with high complexity scores.",
"default_for_language": true
},
"Complexity Checker": {
"supported_languages": ["C", "C++"],
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_library(singularity_lib
analyzers.cpp
application.cpp
security_recommender.cpp
workflow_generator.cpp
)

target_include_directories(singularity_lib
Expand Down
54 changes: 53 additions & 1 deletion src/application.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "singularity/application.hpp"
#include "singularity/repo_analyzer.hpp"
#include "singularity/security_recommender.hpp"
#include "singularity/workflow_generator.hpp"
#include <cstdlib> // For std::exit
#include <iostream>
#include <fstream>
Expand Down Expand Up @@ -78,6 +79,16 @@ bool Application::parse_args(int argc, char** argv) {
else if (arg == "--verbose") {
verbose_ = true;
}
else if (arg == "--workflows") {
generate_workflows_ = true;

// Check if the next argument is an output directory path
if (i + 1 < argc && argv[i+1][0] != '-') {
output_dir_ = argv[++i];
} else {
output_dir_ = "output"; // Default output directory
}
}
else {
std::cerr << "Error: Unknown argument: " << arg << std::endl;
print_usage();
Expand Down Expand Up @@ -108,7 +119,8 @@ void Application::print_usage() {
<< " -v, --version Show version and exit\n"
<< " -r, --repo <url> Repository URL to analyze (currently only GitHub URLs are supported)\n"
<< " --verbose Show verbose output\n"
<< " --security-report [file] Generate a security report, optionally write to file\n";
<< " --security-report [file] Generate a security report, optionally write to file\n"
<< " --workflows [dir] Generate MegaLinter workflow templates in directory (default: output)\n";
}

void Application::print_version() {
Expand Down Expand Up @@ -153,6 +165,17 @@ bool Application::analyze_repo() {
}
}

// Generate workflow templates if requested
if (generate_workflows_) {
if (verbose_) {
std::cout << "Generating MegaLinter workflow templates...\n";
}

if (!generate_workflow_templates(stats)) {
return false;
}
}

return true;
}

Expand Down Expand Up @@ -201,4 +224,33 @@ bool Application::generate_security_report(const LanguageStats& stats) {
}
}

bool Application::generate_workflow_templates(const LanguageStats& stats) {
try {
// Get singleton instance and initialize it
WorkflowGenerator& generator = WorkflowGenerator::instance();

if (!generator.is_initialized()) {
if (!generator.initialize()) {
std::cerr << "Error: Could not initialize workflow generator" << std::endl;
return false;
}
}

// Generate workflow files in the output directory
if (!generator.generate_workflows(stats, output_dir_)) {
std::cerr << "Error: Could not generate workflow templates" << std::endl;
return false;
}

if (verbose_) {
std::cout << "Workflow templates generated in directory: " << output_dir_ << std::endl;
}

return true;
} catch (const std::exception& e) {
std::cerr << "Error generating workflow templates: " << e.what() << std::endl;
return false;
}
}

} // namespace singularity
Loading