@kabran-tecnologia/kabran-config 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/scripts/ci/ci-core.sh +273 -0
- package/src/scripts/ci/ci-runner.sh +9 -0
package/package.json
CHANGED
|
@@ -590,6 +590,279 @@ export_ci_data() {
|
|
|
590
590
|
log_debug "CI data exported to: $output_file"
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
# ==============================================================================
|
|
594
|
+
# OpenTelemetry Metrics Export
|
|
595
|
+
# ==============================================================================
|
|
596
|
+
|
|
597
|
+
# Export CI metrics to OTel Collector via OTLP HTTP
|
|
598
|
+
# Usage: export_ci_metrics_to_otel "$CI_DATA_FILE"
|
|
599
|
+
# Environment: OTEL_ENDPOINT must be set (e.g., http://localhost:4318)
|
|
600
|
+
# Returns: 0 on success, 1 on failure (but never fails the build due to || true usage)
|
|
601
|
+
export_ci_metrics_to_otel() {
|
|
602
|
+
local ci_data_file="${1:-}"
|
|
603
|
+
|
|
604
|
+
# Check if OTEL_ENDPOINT is configured
|
|
605
|
+
if [ -z "${OTEL_ENDPOINT:-}" ]; then
|
|
606
|
+
log_debug "OTEL_ENDPOINT not set, skipping metrics export"
|
|
607
|
+
return 0
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
# Validate input file
|
|
611
|
+
if [ -z "$ci_data_file" ] || [ ! -f "$ci_data_file" ]; then
|
|
612
|
+
log_warn "CI data file not found: $ci_data_file"
|
|
613
|
+
return 1
|
|
614
|
+
fi
|
|
615
|
+
|
|
616
|
+
# Check for curl
|
|
617
|
+
if ! command -v curl &>/dev/null; then
|
|
618
|
+
log_warn "curl not available, skipping OTel metrics export"
|
|
619
|
+
return 1
|
|
620
|
+
fi
|
|
621
|
+
|
|
622
|
+
log_info "Exporting CI metrics to OTel Collector..."
|
|
623
|
+
log_debug " Endpoint: $OTEL_ENDPOINT"
|
|
624
|
+
log_debug " Data file: $ci_data_file"
|
|
625
|
+
|
|
626
|
+
# Extract metrics from CI data
|
|
627
|
+
local total_ms steps_json project_name trace_id ci_passed
|
|
628
|
+
total_ms=$(jq -r '.timing.total_ms // 0' "$ci_data_file" 2>/dev/null)
|
|
629
|
+
steps_json=$(jq -c '.steps // []' "$ci_data_file" 2>/dev/null)
|
|
630
|
+
project_name=$(jq -r '.project.name // "unknown"' "$ci_data_file" 2>/dev/null)
|
|
631
|
+
trace_id=$(jq -r '.trace_context.trace_id // ""' "$ci_data_file" 2>/dev/null)
|
|
632
|
+
|
|
633
|
+
# Determine overall status
|
|
634
|
+
local failed_count
|
|
635
|
+
failed_count=$(echo "$steps_json" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
|
|
636
|
+
if [ "$failed_count" -gt 0 ]; then
|
|
637
|
+
ci_passed="false"
|
|
638
|
+
else
|
|
639
|
+
ci_passed="true"
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
# Get timestamp in nanoseconds (Unix epoch)
|
|
643
|
+
local timestamp_ns
|
|
644
|
+
timestamp_ns=$(date +%s)000000000
|
|
645
|
+
|
|
646
|
+
# Build OTLP metrics payload
|
|
647
|
+
local otlp_payload
|
|
648
|
+
otlp_payload=$(build_otlp_metrics_payload \
|
|
649
|
+
"$project_name" \
|
|
650
|
+
"$total_ms" \
|
|
651
|
+
"$ci_passed" \
|
|
652
|
+
"$steps_json" \
|
|
653
|
+
"$timestamp_ns" \
|
|
654
|
+
"$trace_id")
|
|
655
|
+
|
|
656
|
+
if [ -z "$otlp_payload" ] || [ "$otlp_payload" = "null" ]; then
|
|
657
|
+
log_warn "Failed to build OTLP payload"
|
|
658
|
+
return 1
|
|
659
|
+
fi
|
|
660
|
+
|
|
661
|
+
# Send to OTel Collector with aggressive timeouts
|
|
662
|
+
# --connect-timeout 1: max 1 second to establish connection
|
|
663
|
+
# --max-time 5: max 5 seconds total for the request
|
|
664
|
+
# -f: fail silently on HTTP errors
|
|
665
|
+
local otel_metrics_endpoint="${OTEL_ENDPOINT}/v1/metrics"
|
|
666
|
+
|
|
667
|
+
log_debug "Sending metrics to: $otel_metrics_endpoint"
|
|
668
|
+
|
|
669
|
+
local http_code
|
|
670
|
+
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
671
|
+
--connect-timeout 1 \
|
|
672
|
+
--max-time 5 \
|
|
673
|
+
-X POST \
|
|
674
|
+
-H "Content-Type: application/json" \
|
|
675
|
+
-d "$otlp_payload" \
|
|
676
|
+
"$otel_metrics_endpoint" 2>/dev/null) || {
|
|
677
|
+
log_warn "Failed to send metrics to OTel Collector (connection error)"
|
|
678
|
+
return 1
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
682
|
+
log_success "CI metrics exported to OTel Collector (HTTP $http_code)"
|
|
683
|
+
return 0
|
|
684
|
+
else
|
|
685
|
+
log_warn "OTel Collector returned HTTP $http_code"
|
|
686
|
+
return 1
|
|
687
|
+
fi
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
# Build OTLP JSON payload for metrics
|
|
691
|
+
# Usage: build_otlp_metrics_payload "$project" "$duration_ms" "$passed" "$steps_json" "$timestamp_ns" "$trace_id"
|
|
692
|
+
build_otlp_metrics_payload() {
|
|
693
|
+
local project="$1"
|
|
694
|
+
local duration_ms="$2"
|
|
695
|
+
local passed="$3"
|
|
696
|
+
local steps_json="$4"
|
|
697
|
+
local timestamp_ns="$5"
|
|
698
|
+
local trace_id="${6:-}"
|
|
699
|
+
|
|
700
|
+
# Service attributes
|
|
701
|
+
local service_name="ci-runner"
|
|
702
|
+
local service_version="${CI_CORE_VERSION:-unknown}"
|
|
703
|
+
|
|
704
|
+
# Count steps by status
|
|
705
|
+
local pass_count fail_count skip_count
|
|
706
|
+
pass_count=$(echo "$steps_json" | jq '[.[] | select(.status == "pass")] | length' 2>/dev/null || echo "0")
|
|
707
|
+
fail_count=$(echo "$steps_json" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
|
|
708
|
+
skip_count=$(echo "$steps_json" | jq '[.[] | select(.status == "skip")] | length' 2>/dev/null || echo "0")
|
|
709
|
+
|
|
710
|
+
# Build step duration data points
|
|
711
|
+
local step_duration_points
|
|
712
|
+
step_duration_points=$(echo "$steps_json" | jq -c --arg ts "$timestamp_ns" '
|
|
713
|
+
[.[] | select(.status != "skip") | {
|
|
714
|
+
attributes: ([
|
|
715
|
+
{key: "step.name", value: {stringValue: .name}},
|
|
716
|
+
{key: "step.category", value: {stringValue: (.category // "custom")}},
|
|
717
|
+
{key: "step.status", value: {stringValue: .status}}
|
|
718
|
+
] + (if (.component // "") != "" then [{key: "step.component", value: {stringValue: .component}}] else [] end)),
|
|
719
|
+
startTimeUnixNano: $ts,
|
|
720
|
+
timeUnixNano: $ts,
|
|
721
|
+
asDouble: .duration_ms
|
|
722
|
+
}]
|
|
723
|
+
' 2>/dev/null || echo "[]")
|
|
724
|
+
|
|
725
|
+
# Build resource attributes (conditionally include trace_id)
|
|
726
|
+
local resource_attributes
|
|
727
|
+
if [ -n "$trace_id" ]; then
|
|
728
|
+
resource_attributes=$(jq -n \
|
|
729
|
+
--arg service_name "$service_name" \
|
|
730
|
+
--arg service_version "$service_version" \
|
|
731
|
+
--arg project "$project" \
|
|
732
|
+
--arg trace_id "$trace_id" \
|
|
733
|
+
'[
|
|
734
|
+
{key: "service.name", value: {stringValue: $service_name}},
|
|
735
|
+
{key: "service.version", value: {stringValue: $service_version}},
|
|
736
|
+
{key: "project.name", value: {stringValue: $project}},
|
|
737
|
+
{key: "trace.id", value: {stringValue: $trace_id}}
|
|
738
|
+
]')
|
|
739
|
+
else
|
|
740
|
+
resource_attributes=$(jq -n \
|
|
741
|
+
--arg service_name "$service_name" \
|
|
742
|
+
--arg service_version "$service_version" \
|
|
743
|
+
--arg project "$project" \
|
|
744
|
+
'[
|
|
745
|
+
{key: "service.name", value: {stringValue: $service_name}},
|
|
746
|
+
{key: "service.version", value: {stringValue: $service_version}},
|
|
747
|
+
{key: "project.name", value: {stringValue: $project}}
|
|
748
|
+
]')
|
|
749
|
+
fi
|
|
750
|
+
|
|
751
|
+
# Determine status string
|
|
752
|
+
local status_str="fail"
|
|
753
|
+
if [ "$passed" = "true" ]; then
|
|
754
|
+
status_str="pass"
|
|
755
|
+
fi
|
|
756
|
+
|
|
757
|
+
# Build the full OTLP payload
|
|
758
|
+
jq -n \
|
|
759
|
+
--arg service_version "$service_version" \
|
|
760
|
+
--arg project "$project" \
|
|
761
|
+
--arg timestamp_ns "$timestamp_ns" \
|
|
762
|
+
--arg status_str "$status_str" \
|
|
763
|
+
--argjson duration_ms "$duration_ms" \
|
|
764
|
+
--argjson pass_count "$pass_count" \
|
|
765
|
+
--argjson fail_count "$fail_count" \
|
|
766
|
+
--argjson skip_count "$skip_count" \
|
|
767
|
+
--argjson step_duration_points "$step_duration_points" \
|
|
768
|
+
--argjson resource_attributes "$resource_attributes" \
|
|
769
|
+
'{
|
|
770
|
+
resourceMetrics: [{
|
|
771
|
+
resource: {
|
|
772
|
+
attributes: $resource_attributes
|
|
773
|
+
},
|
|
774
|
+
scopeMetrics: [{
|
|
775
|
+
scope: {
|
|
776
|
+
name: "kabran-config/ci-runner",
|
|
777
|
+
version: $service_version
|
|
778
|
+
},
|
|
779
|
+
metrics: [
|
|
780
|
+
{
|
|
781
|
+
name: "ci.build.duration",
|
|
782
|
+
description: "Total duration of CI build in milliseconds",
|
|
783
|
+
unit: "ms",
|
|
784
|
+
gauge: {
|
|
785
|
+
dataPoints: [{
|
|
786
|
+
attributes: [
|
|
787
|
+
{key: "project", value: {stringValue: $project}},
|
|
788
|
+
{key: "status", value: {stringValue: $status_str}}
|
|
789
|
+
],
|
|
790
|
+
startTimeUnixNano: $timestamp_ns,
|
|
791
|
+
timeUnixNano: $timestamp_ns,
|
|
792
|
+
asDouble: $duration_ms
|
|
793
|
+
}]
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "ci.build.status",
|
|
798
|
+
description: "CI build status counter (1 = occurrence)",
|
|
799
|
+
unit: "1",
|
|
800
|
+
sum: {
|
|
801
|
+
dataPoints: [{
|
|
802
|
+
attributes: [
|
|
803
|
+
{key: "project", value: {stringValue: $project}},
|
|
804
|
+
{key: "status", value: {stringValue: $status_str}}
|
|
805
|
+
],
|
|
806
|
+
startTimeUnixNano: $timestamp_ns,
|
|
807
|
+
timeUnixNano: $timestamp_ns,
|
|
808
|
+
asInt: "1"
|
|
809
|
+
}],
|
|
810
|
+
aggregationTemporality: 2,
|
|
811
|
+
isMonotonic: true
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
name: "ci.step.count",
|
|
816
|
+
description: "Count of CI steps by status",
|
|
817
|
+
unit: "1",
|
|
818
|
+
sum: {
|
|
819
|
+
dataPoints: [
|
|
820
|
+
{
|
|
821
|
+
attributes: [
|
|
822
|
+
{key: "project", value: {stringValue: $project}},
|
|
823
|
+
{key: "status", value: {stringValue: "pass"}}
|
|
824
|
+
],
|
|
825
|
+
startTimeUnixNano: $timestamp_ns,
|
|
826
|
+
timeUnixNano: $timestamp_ns,
|
|
827
|
+
asInt: ($pass_count | tostring)
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
attributes: [
|
|
831
|
+
{key: "project", value: {stringValue: $project}},
|
|
832
|
+
{key: "status", value: {stringValue: "fail"}}
|
|
833
|
+
],
|
|
834
|
+
startTimeUnixNano: $timestamp_ns,
|
|
835
|
+
timeUnixNano: $timestamp_ns,
|
|
836
|
+
asInt: ($fail_count | tostring)
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
attributes: [
|
|
840
|
+
{key: "project", value: {stringValue: $project}},
|
|
841
|
+
{key: "status", value: {stringValue: "skip"}}
|
|
842
|
+
],
|
|
843
|
+
startTimeUnixNano: $timestamp_ns,
|
|
844
|
+
timeUnixNano: $timestamp_ns,
|
|
845
|
+
asInt: ($skip_count | tostring)
|
|
846
|
+
}
|
|
847
|
+
],
|
|
848
|
+
aggregationTemporality: 2,
|
|
849
|
+
isMonotonic: false
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: "ci.step.duration",
|
|
854
|
+
description: "Duration of individual CI steps in milliseconds",
|
|
855
|
+
unit: "ms",
|
|
856
|
+
gauge: {
|
|
857
|
+
dataPoints: $step_duration_points
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
}]
|
|
862
|
+
}]
|
|
863
|
+
}'
|
|
864
|
+
}
|
|
865
|
+
|
|
593
866
|
# ==============================================================================
|
|
594
867
|
# JSON Output Generation
|
|
595
868
|
# ==============================================================================
|
|
@@ -31,6 +31,7 @@ Environment Variables:
|
|
|
31
31
|
CI_OUTPUT_FILE Output file for legacy v1 format
|
|
32
32
|
CI_OUTPUT_FILE_V2 Output file for v2 format (default: docs/quality/ci-result.json)
|
|
33
33
|
CI_CONFIG_FILE Path to project ci-config.sh
|
|
34
|
+
OTEL_ENDPOINT OTel Collector endpoint for metrics export (e.g., http://localhost:4318)
|
|
34
35
|
|
|
35
36
|
Examples:
|
|
36
37
|
# Run all steps
|
|
@@ -232,6 +233,14 @@ if [ "$USE_V2" = "true" ]; then
|
|
|
232
233
|
generate_ci_json "$OUTPUT_FILE" "$CI_PASSED" "$FAILED" "$PROJECT_NAME" "$METADATA"
|
|
233
234
|
fi
|
|
234
235
|
|
|
236
|
+
# ==============================================================================
|
|
237
|
+
# Export Metrics to OTel Collector (if configured)
|
|
238
|
+
# ==============================================================================
|
|
239
|
+
# Fail-safe: telemetry failures NEVER fail the build (RN-01)
|
|
240
|
+
if [ -n "${OTEL_ENDPOINT:-}" ]; then
|
|
241
|
+
export_ci_metrics_to_otel "$INTERMEDIATE_FILE" || true
|
|
242
|
+
fi
|
|
243
|
+
|
|
235
244
|
# Cleanup
|
|
236
245
|
rm -f "$INTERMEDIATE_FILE"
|
|
237
246
|
else
|