archsight 0.2.5 → 0.2.6
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.
- checksums.yaml +4 -4
- data/lib/archsight/cli.rb +8 -2
- data/lib/archsight/import/handlers/go_dep_resolver.rb +84 -0
- data/lib/archsight/import/handlers/go_grapher.rb +104 -54
- data/lib/archsight/import/handlers/go_module_parser.rb +111 -0
- data/lib/archsight/import/handlers/grapher.rb +15 -2
- data/lib/archsight/import/handlers/repository.rb +10 -2
- data/lib/archsight/import/handlers/rest_api.rb +8 -1
- data/lib/archsight/resources/application_component.rb +1 -0
- data/lib/archsight/resources/import.rb +4 -1
- data/lib/archsight/resources/technology_artifact.rb +2 -1
- data/lib/archsight/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a648a562357c7e62c60c815f87e7ef83dbae73b545c0ee3f1ef21e983e705728
|
|
4
|
+
data.tar.gz: b2770c7f3d769a8fc45c9a3d8dfe947454a2054bccf0075206865b1a4b8dbbf5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1cb930e83f808190b15c487226b84db7152981497f0a81746ee96bebd3e10c3b717f2be3765f95e0c42f07725d51cdfdb7bfcfda972b0dd0b76d7108dd99d4d9
|
|
7
|
+
data.tar.gz: a7384b1d52afbe08c5108d0fba87d7c5407eb70c52a7bf6db782ff66036b224637405c49bf105b6afb46f815e5daa3c6cb5a191f69d14834638998a757fa185b
|
data/lib/archsight/cli.rb
CHANGED
|
@@ -176,8 +176,14 @@ module Archsight
|
|
|
176
176
|
require_import_handlers
|
|
177
177
|
|
|
178
178
|
# Create database that loads from resources directory
|
|
179
|
-
# Only load
|
|
180
|
-
|
|
179
|
+
# Only load the kinds that import handlers inspect at runtime (ApplicationComponent
|
|
180
|
+
# and ApplicationInterface for GoGrapher/GoDepResolver cross-referencing, BusinessActor
|
|
181
|
+
# for repository handler owner lookup). Validation is disabled because resources may be
|
|
182
|
+
# in an incomplete state during an in-progress import run.
|
|
183
|
+
db = Archsight::Database.new(resources_dir,
|
|
184
|
+
verbose: options[:verbose],
|
|
185
|
+
only_kinds: %w[Import BusinessActor ApplicationComponent ApplicationInterface],
|
|
186
|
+
verify: false)
|
|
181
187
|
|
|
182
188
|
if options[:dry_run]
|
|
183
189
|
puts "Execution Plan:"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "../handler"
|
|
5
|
+
require_relative "go_module_parser"
|
|
6
|
+
require_relative "../registry"
|
|
7
|
+
|
|
8
|
+
# GoDepResolver handler - second-pass resolver that patches dependsOn relations
|
|
9
|
+
# onto ApplicationComponents generated by GoGrapher.
|
|
10
|
+
#
|
|
11
|
+
# Runs after all GoGraphers complete (iteration 2 of the executor loop) so the
|
|
12
|
+
# database contains the full set of freshly-generated ApplicationComponents.
|
|
13
|
+
# Output goes to a single shared generated/relationships.yaml file (all repos
|
|
14
|
+
# combined), loaded after modules.yaml alphabetically so its version of each
|
|
15
|
+
# component — with dependsOn — takes precedence.
|
|
16
|
+
#
|
|
17
|
+
# One Import:GoDepResolver:* resource is emitted by each GoGrapher run; this
|
|
18
|
+
# handler is registered as a child of GoGrapher via the generates relation so
|
|
19
|
+
# the executor knows to wait until GoGrapher has completed.
|
|
20
|
+
#
|
|
21
|
+
# Configuration:
|
|
22
|
+
# import/config/path - Path to the Go repository root (same as GoGrapher)
|
|
23
|
+
class Archsight::Import::Handlers::GoDepResolver < Archsight::Import::Handler
|
|
24
|
+
include Archsight::Import::Handlers::GoModuleParser
|
|
25
|
+
|
|
26
|
+
def execute
|
|
27
|
+
@path = config("path")
|
|
28
|
+
raise "Missing required config: path" unless @path
|
|
29
|
+
raise "Directory not found: #{@path}" unless File.directory?(@path)
|
|
30
|
+
|
|
31
|
+
import_resource.annotations["import/outputPath"] ||= "generated/relationships.yaml"
|
|
32
|
+
|
|
33
|
+
modules = discover_modules(@path)
|
|
34
|
+
return write_yaml(YAML.dump(self_marker)) if modules.empty?
|
|
35
|
+
|
|
36
|
+
existing_components = database&.instances_by_kind("ApplicationComponent") || {}
|
|
37
|
+
output = +""
|
|
38
|
+
|
|
39
|
+
modules.each do |rel_dir, mod_name|
|
|
40
|
+
mod_dir = rel_dir == "." ? @path : File.join(@path, rel_dir)
|
|
41
|
+
dep_names = compute_deps(mod_dir, mod_name, existing_components)
|
|
42
|
+
next if dep_names.empty?
|
|
43
|
+
|
|
44
|
+
comp_name = component_name(mod_name)
|
|
45
|
+
existing = existing_components[comp_name]
|
|
46
|
+
next unless existing
|
|
47
|
+
|
|
48
|
+
# Rebuild the full ApplicationComponent merging dependsOn into the existing
|
|
49
|
+
# spec (which carries realizedThrough, exposes, etc. from GoGrapher).
|
|
50
|
+
spec = existing.raw.fetch("spec", {}).dup
|
|
51
|
+
spec["dependsOn"] = { "applicationComponents" => dep_names }
|
|
52
|
+
|
|
53
|
+
component = {
|
|
54
|
+
"apiVersion" => "architecture/v1alpha1",
|
|
55
|
+
"kind" => "ApplicationComponent",
|
|
56
|
+
"metadata" => {
|
|
57
|
+
"name" => comp_name,
|
|
58
|
+
"annotations" => existing.annotations.merge(
|
|
59
|
+
"generated/script" => import_resource.name,
|
|
60
|
+
"generated/at" => Time.now.utc.iso8601
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
"spec" => spec
|
|
64
|
+
}
|
|
65
|
+
output << YAML.dump(component)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
write_yaml(output + YAML.dump(self_marker))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def compute_deps(mod_dir, mod_name, existing_components)
|
|
74
|
+
prefix = same_origin_prefix(mod_name)
|
|
75
|
+
return [] unless prefix
|
|
76
|
+
|
|
77
|
+
go_mod_requires(mod_dir)
|
|
78
|
+
.select { |r| r.start_with?(prefix) && r != mod_name }
|
|
79
|
+
.filter_map { |r| component_name(r) if existing_components.key?(component_name(r)) }
|
|
80
|
+
.sort.uniq
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Archsight::Import::Registry.register("go-dep-resolver", Archsight::Import::Handlers::GoDepResolver)
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require "open3"
|
|
4
|
-
require "find"
|
|
5
5
|
require_relative "grapher"
|
|
6
|
+
require_relative "go_module_parser"
|
|
6
7
|
require_relative "../registry"
|
|
7
8
|
|
|
8
9
|
# GoGrapher handler - analyses a Go repository and generates a GraphViz DOT
|
|
9
|
-
# graph of its module/package structure, stored as architecture/modules on
|
|
10
|
+
# graph of its module/package structure, stored as architecture/go/modules on
|
|
10
11
|
# the TechnologyArtifact so it can be rendered in the frontend.
|
|
11
12
|
#
|
|
13
|
+
# Also emits one ApplicationComponent per go.mod module, linked to the repo
|
|
14
|
+
# artifact via realizedThrough. If OpenAPI spec files are found in a module
|
|
15
|
+
# directory, the component gains an exposes relation to the matching interface.
|
|
16
|
+
#
|
|
17
|
+
# dependsOn relations between components are resolved in a second pass by the
|
|
18
|
+
# GoDepResolver handler, which runs after all graphers have seeded the database.
|
|
19
|
+
#
|
|
12
20
|
# Configuration:
|
|
13
|
-
# import/config/path
|
|
14
|
-
# import/config/ranksep
|
|
15
|
-
# import/config/nodesep
|
|
21
|
+
# import/config/path - Path to the Go repository root (go.mod or go.work)
|
|
22
|
+
# import/config/ranksep - Horizontal gap between rank columns (default: 0.6)
|
|
23
|
+
# import/config/nodesep - Vertical gap between nodes in a column (default: 0.15)
|
|
24
|
+
# import/config/interface_visibility - Visibility prefix for detected interfaces (default: Private)
|
|
16
25
|
class Archsight::Import::Handlers::GoGrapher < Archsight::Import::Handlers::Grapher
|
|
26
|
+
include Archsight::Import::Handlers::GoModuleParser
|
|
27
|
+
|
|
28
|
+
OPENAPI_FILENAMES = %w[openapi.yaml openapi.yml openapi.json swagger.yaml swagger.yml swagger.json].freeze
|
|
29
|
+
OPENAPI_SUBDIRS = %w[api docs spec].freeze
|
|
30
|
+
|
|
17
31
|
def self.language_name = "go"
|
|
18
32
|
|
|
19
33
|
def self.applicable?(path)
|
|
@@ -34,56 +48,44 @@ class Archsight::Import::Handlers::GoGrapher < Archsight::Import::Handlers::Grap
|
|
|
34
48
|
has_children.include?(dep) && !pkg_set.include?(dep)
|
|
35
49
|
end
|
|
36
50
|
|
|
37
|
-
# ──
|
|
38
|
-
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Find.find(repo_root) do |path|
|
|
63
|
-
bn = File.basename(path)
|
|
64
|
-
Find.prune if File.directory?(path) && %w[vendor testdata .git node_modules].include?(bn)
|
|
65
|
-
next unless bn == "go.mod"
|
|
66
|
-
|
|
67
|
-
mod_dir = File.dirname(path)
|
|
68
|
-
rel = mod_dir.delete_prefix("#{repo_root}/")
|
|
69
|
-
name = read_module_name(mod_dir)
|
|
70
|
-
modules << [rel, name] if name
|
|
71
|
-
end
|
|
72
|
-
end
|
|
51
|
+
# ── Application resources ─────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def additional_resources(path, modules, artifact_name)
|
|
54
|
+
visibility = config("interface_visibility", default: "Private")
|
|
55
|
+
output = +""
|
|
56
|
+
|
|
57
|
+
modules.each do |rel_dir, mod_name|
|
|
58
|
+
mod_dir = rel_dir == "." ? path : File.join(path, rel_dir)
|
|
59
|
+
specs = detect_openapi_specs(mod_dir)
|
|
60
|
+
existing_interfaces = database&.instances_by_kind("ApplicationInterface") || {}
|
|
61
|
+
interface_names = specs.filter_map { |s| interface_name_from_spec(s, visibility: visibility) }
|
|
62
|
+
.select { |n| existing_interfaces.key?(n) }
|
|
63
|
+
|
|
64
|
+
comp_spec = { "realizedThrough" => { "technologyArtifacts" => [artifact_name] } }
|
|
65
|
+
comp_spec["exposes"] = { "applicationInterfaces" => interface_names } if interface_names.any?
|
|
66
|
+
|
|
67
|
+
# Build directly without resource_yaml so the component is not tracked in
|
|
68
|
+
# the generates spec — ApplicationComponents are seed resources the user
|
|
69
|
+
# is expected to keep and refine, not auto-regenerated on each run.
|
|
70
|
+
component = untracked_resource_yaml(
|
|
71
|
+
kind: "ApplicationComponent",
|
|
72
|
+
name: component_name(mod_name),
|
|
73
|
+
spec: comp_spec
|
|
74
|
+
)
|
|
75
|
+
output << YAML.dump(component)
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
nil
|
|
78
|
+
# Emit a GoDepResolver import so dependsOn relations can be resolved in a
|
|
79
|
+
# second pass, after all graphers have run and the database has been reloaded
|
|
80
|
+
# with the full set of ApplicationComponents.
|
|
81
|
+
resolver_name = "Import:GoDepResolver:#{artifact_name.delete_prefix("Repo:")}"
|
|
82
|
+
output << YAML.dump(import_yaml(
|
|
83
|
+
name: resolver_name,
|
|
84
|
+
handler: "go-dep-resolver",
|
|
85
|
+
config: { "path" => path }
|
|
86
|
+
))
|
|
87
|
+
|
|
88
|
+
output
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
# ── Package collection ────────────────────────────────────────────────────
|
|
@@ -93,7 +95,7 @@ class Archsight::Import::Handlers::GoGrapher < Archsight::Import::Handlers::Grap
|
|
|
93
95
|
mod_names = modules.map { |_, mod_name| mod_name }
|
|
94
96
|
all_pkgs = {}
|
|
95
97
|
|
|
96
|
-
modules.
|
|
98
|
+
modules.map(&:first).each do |rel_dir|
|
|
97
99
|
mod_dir = rel_dir == "." ? repo_root : File.join(repo_root, rel_dir)
|
|
98
100
|
cmd = ["go", "list", "-e", "-f", "{{.ImportPath}}|||{{join .Imports \" \"}}", "./..."]
|
|
99
101
|
cmd.insert(2, "-mod=vendor") if File.directory?(File.join(mod_dir, "vendor"))
|
|
@@ -122,6 +124,54 @@ class Archsight::Import::Handlers::GoGrapher < Archsight::Import::Handlers::Grap
|
|
|
122
124
|
|
|
123
125
|
all_pkgs
|
|
124
126
|
end
|
|
127
|
+
|
|
128
|
+
# ── OpenAPI detection ─────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
def untracked_resource_yaml(kind:, name:, spec: {})
|
|
131
|
+
{
|
|
132
|
+
"apiVersion" => "architecture/v1alpha1",
|
|
133
|
+
"kind" => kind,
|
|
134
|
+
"metadata" => {
|
|
135
|
+
"name" => name,
|
|
136
|
+
"annotations" => {
|
|
137
|
+
"generated/script" => import_resource.name,
|
|
138
|
+
"generated/at" => Time.now.utc.iso8601
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"spec" => spec
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def detect_openapi_specs(mod_dir)
|
|
146
|
+
candidates = OPENAPI_FILENAMES.map { |f| File.join(mod_dir, f) }
|
|
147
|
+
OPENAPI_SUBDIRS.each do |sub|
|
|
148
|
+
OPENAPI_FILENAMES.each { |f| candidates << File.join(mod_dir, sub, f) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
candidates.filter_map do |path|
|
|
152
|
+
next unless File.file?(path)
|
|
153
|
+
|
|
154
|
+
begin
|
|
155
|
+
content = File.read(path)
|
|
156
|
+
doc = path.end_with?(".json") ? JSON.parse(content) : YAML.safe_load(content)
|
|
157
|
+
doc.is_a?(Hash) ? doc : nil
|
|
158
|
+
rescue StandardError
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def interface_name_from_spec(spec, visibility: "Private")
|
|
165
|
+
info = spec["info"] || {}
|
|
166
|
+
title = (info["title"] || "").strip
|
|
167
|
+
return nil if title.empty?
|
|
168
|
+
|
|
169
|
+
version = (info["version"] || "1.0").to_s
|
|
170
|
+
api_name = title.split(/[\s\-_]/).map(&:capitalize).join
|
|
171
|
+
version_str = version.split(".").first.to_s.sub(/^v/i, "")
|
|
172
|
+
vis = visibility.split("-").map(&:capitalize).join
|
|
173
|
+
"#{vis}:#{api_name}:v#{version_str}:RestAPI"
|
|
174
|
+
end
|
|
125
175
|
end
|
|
126
176
|
|
|
127
177
|
Archsight::Import::Registry.register("go-grapher", Archsight::Import::Handlers::GoGrapher)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "find"
|
|
4
|
+
|
|
5
|
+
# Shared Go module parsing utilities included by GoGrapher and GoDepResolver.
|
|
6
|
+
#
|
|
7
|
+
# Provides go.mod discovery, module name reading, require parsing, and the
|
|
8
|
+
# component_name convention (strip SCM host, join path segments with ":").
|
|
9
|
+
module Archsight::Import::Handlers::GoModuleParser
|
|
10
|
+
# Discover all Go modules in a repository root.
|
|
11
|
+
# Handles go.work workspaces, single-module repos, and multi-module monorepos
|
|
12
|
+
# (root go.mod plus subdirectory go.mod files without go.work).
|
|
13
|
+
#
|
|
14
|
+
# @param repo_root [String] Absolute path to the repository root
|
|
15
|
+
# @return [Array<Array<String>>] List of [rel_dir, mod_name] pairs
|
|
16
|
+
def discover_modules(repo_root)
|
|
17
|
+
work_file = File.join(repo_root, "go.work")
|
|
18
|
+
modules = []
|
|
19
|
+
|
|
20
|
+
if File.exist?(work_file)
|
|
21
|
+
content = File.read(work_file)
|
|
22
|
+
dirs = if (block_match = content.match(/\buse\s*\((.*?)\)/m))
|
|
23
|
+
block_match[1].split.map(&:strip).reject(&:empty?)
|
|
24
|
+
else
|
|
25
|
+
content.scan(/\buse\s+(\S+)/).flatten
|
|
26
|
+
end
|
|
27
|
+
dirs.each do |d|
|
|
28
|
+
rel = d == "." ? "" : d.delete_prefix("./")
|
|
29
|
+
abs_dir = rel.empty? ? repo_root : File.join(repo_root, rel)
|
|
30
|
+
mod_name = read_module_name(abs_dir)
|
|
31
|
+
modules << [rel.empty? ? "." : rel, mod_name] if mod_name
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
root_mod = read_module_name(repo_root)
|
|
35
|
+
modules << [".", root_mod] if root_mod
|
|
36
|
+
|
|
37
|
+
# Also scan subdirectories for additional go.mod files (multi-module monorepo without go.work)
|
|
38
|
+
Find.find(repo_root) do |path|
|
|
39
|
+
bn = File.basename(path)
|
|
40
|
+
Find.prune if File.directory?(path) && %w[vendor testdata .git node_modules].include?(bn)
|
|
41
|
+
next unless bn == "go.mod"
|
|
42
|
+
|
|
43
|
+
mod_dir = File.dirname(path)
|
|
44
|
+
next if mod_dir == repo_root # already added above
|
|
45
|
+
|
|
46
|
+
rel = mod_dir.delete_prefix("#{repo_root}/")
|
|
47
|
+
name = read_module_name(mod_dir)
|
|
48
|
+
modules << [rel, name] if name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
modules
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Read the module name declared in go.mod.
|
|
56
|
+
# @return [String, nil] Module path or nil if go.mod absent / no module line
|
|
57
|
+
def read_module_name(mod_dir)
|
|
58
|
+
gomod = File.join(mod_dir, "go.mod")
|
|
59
|
+
return nil unless File.exist?(gomod)
|
|
60
|
+
|
|
61
|
+
File.foreach(gomod) do |line|
|
|
62
|
+
m = line.match(/^\s*module\s+(\S+)/)
|
|
63
|
+
return m[1] if m
|
|
64
|
+
end
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Parse require directives from go.mod, handling both block and single-line forms.
|
|
69
|
+
# @return [Array<String>] Unique module paths listed in require directives
|
|
70
|
+
def go_mod_requires(mod_dir)
|
|
71
|
+
gomod = File.join(mod_dir, "go.mod")
|
|
72
|
+
return [] unless File.exist?(gomod)
|
|
73
|
+
|
|
74
|
+
content = File.read(gomod)
|
|
75
|
+
paths = []
|
|
76
|
+
|
|
77
|
+
# Block form: require ( ... )
|
|
78
|
+
content.scan(/\brequire\s*\(([^)]*)\)/m) do |block|
|
|
79
|
+
block[0].each_line do |line|
|
|
80
|
+
stripped = line.strip.split("//").first&.strip
|
|
81
|
+
mod_path = stripped&.split&.first
|
|
82
|
+
paths << mod_path if mod_path && !mod_path.empty?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Single-line form: require module/path vX.Y.Z (strip block forms first to avoid double-counting)
|
|
87
|
+
content.gsub(/\brequire\s*\([^)]*\)/m, "").scan(/^\s*require\s+(\S+)/) do |m|
|
|
88
|
+
paths << m[0]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
paths.uniq
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Return the SCM host+org prefix shared by modules in the same org, e.g. "github.com/ionos-cloud/".
|
|
95
|
+
# @return [String, nil] Prefix with trailing slash, or nil for single-segment names
|
|
96
|
+
def same_origin_prefix(mod_name)
|
|
97
|
+
parts = mod_name.split("/")
|
|
98
|
+
return nil if parts.length < 2
|
|
99
|
+
|
|
100
|
+
"#{parts[0, 2].join("/")}/"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Convert a Go module path to an ApplicationComponent name.
|
|
104
|
+
# Strips the SCM host segment and joins remaining path segments with ":".
|
|
105
|
+
# "github.com/ionos-cloud/event-gateway/pkg" → "ionos-cloud:event-gateway:pkg"
|
|
106
|
+
def component_name(mod_name)
|
|
107
|
+
parts = mod_name.split("/")
|
|
108
|
+
parts.shift
|
|
109
|
+
parts.join(":")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -53,6 +53,11 @@ class Archsight::Import::Handlers::Grapher < Archsight::Import::Handler
|
|
|
53
53
|
raise "Missing required config: path" unless @path
|
|
54
54
|
raise "Directory not found: #{@path}" unless File.directory?(@path)
|
|
55
55
|
|
|
56
|
+
# Consolidate all grapher output into a single shared file unless an
|
|
57
|
+
# explicit output path is already set (e.g. via grapherOutputPath on the
|
|
58
|
+
# parent Repository import).
|
|
59
|
+
import_resource.annotations["import/outputPath"] ||= "generated/modules.yaml"
|
|
60
|
+
|
|
56
61
|
@ranksep = config("ranksep", default: "0.6").to_f
|
|
57
62
|
@nodesep = config("nodesep", default: "0.15").to_f
|
|
58
63
|
|
|
@@ -71,14 +76,16 @@ class Archsight::Import::Handlers::Grapher < Archsight::Import::Handler
|
|
|
71
76
|
ranksep: @ranksep, nodesep: @nodesep)
|
|
72
77
|
|
|
73
78
|
progress.update("Generating resource")
|
|
79
|
+
repo_name = artifact_name(@path)
|
|
74
80
|
resource = resource_yaml(
|
|
75
81
|
kind: "TechnologyArtifact",
|
|
76
|
-
name:
|
|
82
|
+
name: repo_name,
|
|
77
83
|
annotations: { annotation_key => dot_content },
|
|
78
84
|
spec: {}
|
|
79
85
|
)
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
extra = additional_resources(@path, modules, repo_name)
|
|
88
|
+
write_yaml(YAML.dump(resource) + extra + YAML.dump(self_marker))
|
|
82
89
|
write_generates_meta
|
|
83
90
|
end
|
|
84
91
|
|
|
@@ -535,6 +542,12 @@ class Archsight::Import::Handlers::Grapher < Archsight::Import::Handler
|
|
|
535
542
|
has_children.include?(dep)
|
|
536
543
|
end
|
|
537
544
|
|
|
545
|
+
# Hook: subclasses may override to emit additional YAML resources (as a
|
|
546
|
+
# concatenated YAML string) alongside the TechnologyArtifact in the output.
|
|
547
|
+
def additional_resources(_path, _modules, _artifact_name)
|
|
548
|
+
""
|
|
549
|
+
end
|
|
550
|
+
|
|
538
551
|
# ── Artifact name ─────────────────────────────────────────────────────────
|
|
539
552
|
|
|
540
553
|
def artifact_name(path)
|
|
@@ -115,9 +115,12 @@ class Archsight::Import::Handlers::Repository < Archsight::Import::Handler
|
|
|
115
115
|
run_git(%w[git fetch --quiet], @path)
|
|
116
116
|
return if empty_repository? # Skip merge for empty repos
|
|
117
117
|
|
|
118
|
-
#
|
|
118
|
+
# FETCH_HEAD may be absent when git fetch brings nothing new (e.g. no
|
|
119
|
+
# tracking refs configured, or the remote has no branches). Skip merge.
|
|
120
|
+
fetch_head = resolve_ref("FETCH_HEAD")
|
|
121
|
+
return unless fetch_head
|
|
122
|
+
|
|
119
123
|
current_head = run_git(%w[git rev-parse HEAD], @path).strip
|
|
120
|
-
fetch_head = run_git(%w[git rev-parse FETCH_HEAD], @path).strip
|
|
121
124
|
return if current_head == fetch_head # Already up-to-date
|
|
122
125
|
|
|
123
126
|
run_git(%w[git merge --ff-only FETCH_HEAD], @path)
|
|
@@ -133,6 +136,11 @@ class Archsight::Import::Handlers::Repository < Archsight::Import::Handler
|
|
|
133
136
|
!status.success?
|
|
134
137
|
end
|
|
135
138
|
|
|
139
|
+
def resolve_ref(ref)
|
|
140
|
+
out, _, status = Open3.capture3("git", "rev-parse", ref, chdir: @path)
|
|
141
|
+
status.success? ? out.strip : nil
|
|
142
|
+
end
|
|
143
|
+
|
|
136
144
|
# Run a git command safely using array form to prevent shell injection
|
|
137
145
|
# @param command [Array<String>] Command and arguments as array
|
|
138
146
|
# @param dir [String] Working directory
|
|
@@ -44,6 +44,12 @@ class Archsight::Import::Handlers::RestApi < Archsight::Import::Handler
|
|
|
44
44
|
progress.update("Downloading OpenAPI spec for #{@name}")
|
|
45
45
|
openapi_doc = fetch_openapi_spec(@spec_url)
|
|
46
46
|
|
|
47
|
+
if openapi_doc.nil?
|
|
48
|
+
progress.warn("Spec not found (404) for #{@name} at #{@spec_url} — skipping")
|
|
49
|
+
write_yaml(YAML.dump(self_marker))
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
47
53
|
# Parse schemas and generate DataObjects first (needed for interface relations)
|
|
48
54
|
progress.update("Extracting DataObjects from #{@name} schemas")
|
|
49
55
|
parser = Archsight::Import::Handlers::OpenAPISchemaParser.new(openapi_doc)
|
|
@@ -141,8 +147,9 @@ class Archsight::Import::Handlers::RestApi < Archsight::Import::Handler
|
|
|
141
147
|
when Net::HTTPSuccess
|
|
142
148
|
parse_spec_content(response.body)
|
|
143
149
|
when Net::HTTPRedirection
|
|
144
|
-
# Follow redirect
|
|
145
150
|
fetch_openapi_spec(response["location"])
|
|
151
|
+
when Net::HTTPNotFound
|
|
152
|
+
nil
|
|
146
153
|
else
|
|
147
154
|
raise "Failed to fetch OpenAPI spec from #{uri}: #{response.code} #{response.message}"
|
|
148
155
|
end
|
|
@@ -343,4 +343,5 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
|
|
|
343
343
|
relation :servedBy, :technologyComponents, :TechnologySystemSoftware
|
|
344
344
|
relation :exposes, :applicationInterfaces, :ApplicationInterface
|
|
345
345
|
relation :dependsOn, :applicationInterfaces, :ApplicationInterface
|
|
346
|
+
relation :dependsOn, :applicationComponents, :ApplicationComponent
|
|
346
347
|
end
|
|
@@ -42,7 +42,10 @@ class Archsight::Resources::Import < Archsight::Resources::Base
|
|
|
42
42
|
gitlab github repository
|
|
43
43
|
rest-api rest-api-index
|
|
44
44
|
jira-discover jira-metrics
|
|
45
|
-
go-grapher
|
|
45
|
+
go-grapher go-dep-resolver
|
|
46
|
+
cpp-grapher ruby-grapher python-grapher
|
|
47
|
+
java-grapher javascript-grapher
|
|
48
|
+
rust-grapher crystal-grapher elixir-grapher
|
|
46
49
|
]
|
|
47
50
|
|
|
48
51
|
# Output configuration
|
|
@@ -229,7 +229,8 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
|
|
|
229
229
|
"go" => "Go", "python" => "Python", "java" => "Java",
|
|
230
230
|
"typescript" => "TypeScript", "javascript" => "JavaScript",
|
|
231
231
|
"rust" => "Rust", "ruby" => "Ruby", "crystal" => "Crystal",
|
|
232
|
-
"csharp" => "C#", "zig" => "Zig", "elixir" => "Elixir"
|
|
232
|
+
"csharp" => "C#", "zig" => "Zig", "elixir" => "Elixir",
|
|
233
|
+
"cpp" => "C++"
|
|
233
234
|
}.each do |lang, label|
|
|
234
235
|
annotation "architecture/#{lang}/modules",
|
|
235
236
|
description: "GraphViz DOT — #{label} module/package structure",
|
data/lib/archsight/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: archsight
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vincent Landgraf
|
|
@@ -269,7 +269,9 @@ files:
|
|
|
269
269
|
- lib/archsight/import/handlers/elixir_grapher.rb
|
|
270
270
|
- lib/archsight/import/handlers/github.rb
|
|
271
271
|
- lib/archsight/import/handlers/gitlab.rb
|
|
272
|
+
- lib/archsight/import/handlers/go_dep_resolver.rb
|
|
272
273
|
- lib/archsight/import/handlers/go_grapher.rb
|
|
274
|
+
- lib/archsight/import/handlers/go_module_parser.rb
|
|
273
275
|
- lib/archsight/import/handlers/grapher.rb
|
|
274
276
|
- lib/archsight/import/handlers/java_grapher.rb
|
|
275
277
|
- lib/archsight/import/handlers/javascript_grapher.rb
|