SshExportWorkflowTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. using Tests.EndToEnd.Infra;
  2. using Xunit.Abstractions;
  3. namespace Tests.EndToEnd.ExporterTests;
  4. [Collection("Yaml CLI tests")]
  5. public class SshExportWorkflowTests(
  6. TempYamlCliFixture fs,
  7. ITestOutputHelper outputHelper)
  8. : IClassFixture<TempYamlCliFixture> {
  9. private async Task<(string output, string yaml)> ExecuteAsync(params string[] args) {
  10. outputHelper.WriteLine($"rpk {string.Join(" ", args)}");
  11. var output = await YamlCliTestHost.RunAsync(
  12. args,
  13. fs.Root,
  14. outputHelper,
  15. "config.yaml");
  16. outputHelper.WriteLine(output);
  17. var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
  18. return (output, yaml);
  19. }
  20. [Fact]
  21. public async Task ssh_export_basic_workflow_test() {
  22. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  23. version: 1
  24. resources:
  25. - kind: System
  26. type: vm
  27. name: vm-web01
  28. tags:
  29. - prod
  30. labels:
  31. ip: 192.168.1.10
  32. ssh_user: ubuntu
  33. - kind: System
  34. type: vm
  35. name: vm-db01
  36. labels:
  37. ip: 192.168.1.20
  38. ssh_user: postgres
  39. """);
  40. (var output, var _) = await ExecuteAsync(
  41. "ssh", "export"
  42. );
  43. Assert.Equal("""
  44. Generated SSH Config:
  45. Host vm-db01
  46. HostName 192.168.1.20
  47. User postgres
  48. Host vm-web01
  49. HostName 192.168.1.10
  50. User ubuntu
  51. """, output);
  52. }
  53. [Fact]
  54. public async Task ssh_export_with_defaults_test() {
  55. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  56. version: 1
  57. resources:
  58. - kind: System
  59. type: vm
  60. name: vm1
  61. labels:
  62. ip: 10.0.0.1
  63. """);
  64. (var output, var _) = await ExecuteAsync(
  65. "ssh", "export",
  66. "--default-user", "admin",
  67. "--default-port", "2222",
  68. "--default-identity", "~/.ssh/id_rsa"
  69. );
  70. Assert.Equal("""
  71. Generated SSH Config:
  72. Host vm1
  73. HostName 10.0.0.1
  74. User admin
  75. Port 2222
  76. IdentityFile ~/.ssh/id_rsa
  77. """, output);
  78. }
  79. [Fact]
  80. public async Task ssh_export_respects_tag_filter() {
  81. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  82. version: 1
  83. resources:
  84. - kind: System
  85. type: vm
  86. name: prod-vm
  87. tags:
  88. - prod
  89. labels:
  90. ip: 10.0.0.1
  91. - kind: System
  92. type: vm
  93. name: staging-vm
  94. tags:
  95. - staging
  96. labels:
  97. ip: 10.0.0.2
  98. """);
  99. (var output, var _) = await ExecuteAsync(
  100. "ssh", "export",
  101. "--include-tags", "prod"
  102. );
  103. Assert.Contains("Host prod-vm", output);
  104. Assert.DoesNotContain("Host staging-vm", output);
  105. }
  106. [Fact]
  107. public async Task ssh_export_is_sorted_by_name() {
  108. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  109. version: 1
  110. resources:
  111. - kind: System
  112. type: vm
  113. name: b-host
  114. labels:
  115. ip: 10.0.0.2
  116. - kind: System
  117. type: vm
  118. name: a-host
  119. labels:
  120. ip: 10.0.0.1
  121. """);
  122. (var output, var _) = await ExecuteAsync(
  123. "ssh", "export"
  124. );
  125. var aIndex = output.IndexOf("Host a-host", StringComparison.Ordinal);
  126. var bIndex = output.IndexOf("Host b-host", StringComparison.Ordinal);
  127. Assert.True(aIndex < bIndex);
  128. }
  129. [Fact]
  130. public async Task ssh_export_uses_hostname_label_when_no_ip() {
  131. // ssh-config-export.md §1 & §9: `hostname` is an accepted address
  132. // fallback when `ip` is not set.
  133. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  134. version: 1
  135. resources:
  136. - kind: System
  137. type: vm
  138. name: vm-host
  139. labels:
  140. hostname: vm-host.lan
  141. """);
  142. (var output, var _) = await ExecuteAsync("ssh", "export");
  143. Assert.Contains("Host vm-host", output);
  144. Assert.Contains("HostName vm-host.lan", output);
  145. }
  146. [Fact]
  147. public async Task ssh_export_uses_ansible_host_label_when_no_ip_or_hostname() {
  148. // ssh-config-export.md §1 & §9: `ansible_host` is the third fallback
  149. // for the address.
  150. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  151. version: 1
  152. resources:
  153. - kind: System
  154. type: vm
  155. name: vm-ansible
  156. labels:
  157. ansible_host: 10.0.0.99
  158. """);
  159. (var output, var _) = await ExecuteAsync("ssh", "export");
  160. Assert.Contains("Host vm-ansible", output);
  161. Assert.Contains("HostName 10.0.0.99", output);
  162. }
  163. [Fact]
  164. public async Task ssh_export_falls_back_from_ssh_user_to_ansible_user() {
  165. // ssh-config-export.md §9 fallback chain: ssh_user → ansible_user →
  166. // CLI default. A resource with only `ansible_user` should still
  167. // produce a User line.
  168. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  169. version: 1
  170. resources:
  171. - kind: System
  172. type: vm
  173. name: vm-prefer
  174. labels:
  175. ip: 10.0.0.1
  176. ssh_user: prefer-this
  177. ansible_user: ignore-this
  178. - kind: System
  179. type: vm
  180. name: vm-fallback
  181. labels:
  182. ip: 10.0.0.2
  183. ansible_user: from-ansible
  184. """);
  185. (var output, var _) = await ExecuteAsync("ssh", "export");
  186. Assert.Contains("""
  187. Host vm-fallback
  188. HostName 10.0.0.2
  189. User from-ansible
  190. """, output);
  191. Assert.Contains("""
  192. Host vm-prefer
  193. HostName 10.0.0.1
  194. User prefer-this
  195. """, output);
  196. Assert.DoesNotContain("ignore-this", output);
  197. }
  198. [Fact]
  199. public async Task ssh_export_falls_back_from_ssh_port_to_ansible_port() {
  200. // ssh-config-export.md §9 fallback chain: ssh_port → ansible_port →
  201. // CLI default. Note: the generator omits `Port` if the resolved port
  202. // equals 22, so test with a non-default value.
  203. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  204. version: 1
  205. resources:
  206. - kind: System
  207. type: vm
  208. name: vm-prefer
  209. labels:
  210. ip: 10.0.0.1
  211. ssh_port: "2200"
  212. ansible_port: "9999"
  213. - kind: System
  214. type: vm
  215. name: vm-fallback
  216. labels:
  217. ip: 10.0.0.2
  218. ansible_port: "2222"
  219. """);
  220. (var output, var _) = await ExecuteAsync("ssh", "export");
  221. Assert.Contains("""
  222. Host vm-fallback
  223. HostName 10.0.0.2
  224. Port 2222
  225. """, output);
  226. Assert.Contains("""
  227. Host vm-prefer
  228. HostName 10.0.0.1
  229. Port 2200
  230. """, output);
  231. Assert.DoesNotContain("9999", output);
  232. }
  233. [Fact]
  234. public async Task ssh_export_honours_per_resource_ssh_port_and_identity_file_labels() {
  235. // ssh-config-export.md §2: per-resource ssh_port and
  236. // ssh_identity_file labels are emitted as Port / IdentityFile lines
  237. // even when no CLI defaults are passed.
  238. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  239. version: 1
  240. resources:
  241. - kind: System
  242. type: vm
  243. name: vm-custom
  244. labels:
  245. ip: 10.0.0.1
  246. ssh_user: ubuntu
  247. ssh_port: "2222"
  248. ssh_identity_file: ~/.ssh/id_custom
  249. """);
  250. (var output, var _) = await ExecuteAsync("ssh", "export");
  251. Assert.Contains("""
  252. Host vm-custom
  253. HostName 10.0.0.1
  254. User ubuntu
  255. Port 2222
  256. IdentityFile ~/.ssh/id_custom
  257. """, output);
  258. }
  259. [Fact]
  260. public async Task ssh_export_skips_resources_without_address() {
  261. await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
  262. version: 1
  263. resources:
  264. - kind: System
  265. type: vm
  266. name: vm-with-ip
  267. labels:
  268. ip: 10.0.0.1
  269. - kind: System
  270. type: vm
  271. name: vm-no-ip
  272. """);
  273. (var output, var _) = await ExecuteAsync(
  274. "ssh", "export"
  275. );
  276. Assert.Contains("Host vm-with-ip", output);
  277. Assert.DoesNotContain("vm-no-ip", output);
  278. }
  279. }