Class: KeySloth::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/keysloth/cli.rb

Overview

Интерфейс командной строки для KeySloth

Предоставляет команды pull, push и help для управления секретами. Использует Thor для парсинга аргументов командной строки.

Examples:

Использование CLI

# Получение секретов
keysloth pull --repo git@github.com:company/secrets.git --password secret

# Отправка секретов
keysloth push --repo git@github.com:company/secrets.git --password secret

# Справка
keysloth help

Author:

  • KeySloth Team

Since:

  • 0.1.0

Instance Method Summary collapse

Instance Method Details

#help(command = nil) ⇒ Object

Since:

  • 0.1.0



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/keysloth/cli.rb', line 407

def help(command = nil)
  if command
    super
  else
    puts "      KeySloth v\#{KeySloth::VERSION} - \u0423\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u0448\u0438\u0444\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u043C\u0438 \u0441\u0435\u043A\u0440\u0435\u0442\u0430\u043C\u0438 \u0432 Git \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u0445\n\n      \u0418\u0421\u041F\u041E\u041B\u042C\u0417\u041E\u0412\u0410\u041D\u0418\u0415:\n        keysloth COMMAND [OPTIONS]\n\n      \u041A\u041E\u041C\u0410\u041D\u0414\u042B:\n        init     \u0418\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u0441 KeySloth\n        pull     \u041F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0438 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0438\u0437 Git \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\n        push     \u0417\u0430\u0448\u0438\u0444\u0440\u043E\u0432\u0430\u0442\u044C \u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0432 Git \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439\#{'  '}\n        status   \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0445 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n        validate \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0446\u0435\u043B\u043E\u0441\u0442\u043D\u043E\u0441\u0442\u044C \u0444\u0430\u0439\u043B\u043E\u0432 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n        restore  \u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0438\u0437 \u0440\u0435\u0437\u0435\u0440\u0432\u043D\u043E\u0439 \u043A\u043E\u043F\u0438\u0438\n        version  \u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u0435\u0440\u0441\u0438\u044E KeySloth\n        help     \u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u044D\u0442\u0443 \u0441\u043F\u0440\u0430\u0432\u043A\u0443\n\n      \u0413\u041B\u041E\u0411\u0410\u041B\u042C\u041D\u042B\u0415 \u041E\u041F\u0426\u0418\u0418:\n        -v, --verbose    \u041F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0435 \u043B\u043E\u0433\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 (DEBUG \u0443\u0440\u043E\u0432\u0435\u043D\u044C)\n        -q, --quiet      \u0422\u0438\u0445\u0438\u0439 \u0440\u0435\u0436\u0438\u043C (\u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0448\u0438\u0431\u043A\u0438)\n        -c, --config     \u041F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 .keyslothrc\n\n      \u041F\u0420\u0418\u041C\u0415\u0420\u042B:\n        # \u0418\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0435\u043A\u0442\n        keysloth init -r [email protected]:company/secrets.git\n\n        # \u041F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0438\u0437 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\n        keysloth pull -r [email protected]:company/secrets.git -p mypassword\n\n        # \u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439\n        keysloth push -r [email protected]:company/secrets.git -p mypassword -m \"Update certificates\"\n\n        # \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0438 \u0446\u0435\u043B\u043E\u0441\u0442\u043D\u043E\u0441\u0442\u044C\n        keysloth status\n        keysloth validate\n\n      \u0414\u043B\u044F \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0439 \u0441\u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E \u043A\u043E\u043C\u0430\u043D\u0434\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435: keysloth help COMMAND\n    HELP\n  end\nend\n"

#init ⇒ Object

Since:

  • 0.1.0



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/keysloth/cli.rb', line 299

def init
  setup_logger

  logger.info("\n=== Инициализируем KeySloth проект ===")

  begin
    file_manager = FileManager.new(logger)

    config_path = '.keyslothrc'
    gitignore_path = '.gitignore'
    secrets_path = options[:path]

    # Проверяем существование конфигурации
    if File.exist?(config_path) && !options[:force]
      logger.error("Файл #{config_path} уже существует. Используйте --force для перезаписи.")
      exit 1
    end

    # Создаем конфигурационный файл
    config_content = "      # \u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F KeySloth\n      repo_url: \"\#{options[:repo]}\"\n      branch: \"\#{options[:branch]}\"\n      local_path: \"\#{secrets_path}\"\n      backup_count: 3\n    YAML\n\n    File.write(config_path, config_content)\n    logger.info(\"\u0421\u043E\u0437\u0434\u0430\u043D \u0444\u0430\u0439\u043B \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438: \#{config_path}\")\n\n    # \u0421\u043E\u0437\u0434\u0430\u0435\u043C \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u044E \u0434\u043B\u044F \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n    file_manager.ensure_directory(secrets_path)\n    logger.info(\"\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u044F \u0434\u043B\u044F \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432: \#{secrets_path}\")\n\n    # \u041E\u0431\u043D\u043E\u0432\u043B\u044F\u0435\u043C .gitignore\n    gitignore_entry = \"\\n# KeySloth \u0441\u0435\u043A\u0440\u0435\u0442\u044B\\n\#{secrets_path}/\\n.keyslothrc\\n\"\n\n    if File.exist?(gitignore_path)\n      gitignore_content = File.read(gitignore_path)\n      if gitignore_content.include?(secrets_path)\n        logger.info('\u0424\u0430\u0439\u043B .gitignore \u0443\u0436\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F KeySloth')\n      else\n        File.write(gitignore_path, gitignore_content + gitignore_entry)\n        logger.info('\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D \u0444\u0430\u0439\u043B .gitignore')\n      end\n    else\n      File.write(gitignore_path, gitignore_entry.strip)\n      logger.info('\u0421\u043E\u0437\u0434\u0430\u043D \u0444\u0430\u0439\u043B .gitignore')\n    end\n\n    # \u0421\u043E\u0437\u0434\u0430\u0435\u043C README \u0434\u043B\u044F \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0438 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n    readme_path = File.join(secrets_path, 'README.md')\n    readme_content = <<~MARKDOWN\n      # \u0421\u0435\u043A\u0440\u0435\u0442\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u0430\n\n      \u042D\u0442\u0430 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u044F \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u0437\u0430\u0448\u0438\u0444\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0441\u0435\u043A\u0440\u0435\u0442\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u044B\u0435 KeySloth.\n\n      ## \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435\n\n      ### \u041F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0435 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n      ```bash\n      keysloth pull\n      ```\n\n      ### \u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\n      ```bash\n      keysloth push\n      ```\n\n      ### \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F\n      ```bash\n      keysloth status\n      ```\n\n      ## \u0422\u0438\u043F\u044B \u0444\u0430\u0439\u043B\u043E\u0432\n      \u041F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u043B\u044E\u0431\u044B\u0435 \u0442\u0438\u043F\u044B \u0444\u0430\u0439\u043B\u043E\u0432. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0438\u0433\u043D\u043E\u0440\u0438\u0440\u0443\u044E\u0442\u0441\u044F:\n      - `*.enc` (\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B \u0448\u0438\u0444\u0440\u043E\u0432\u0430\u043D\u0438\u044F)\n      - \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 `.git/`\n      - `.DS_Store`, `Thumbs.db`\n      - \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 `README.md` \u0432 \u044D\u0442\u043E\u0439 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u0438\n\n      **\u0412\u041D\u0418\u041C\u0410\u041D\u0418\u0415:** \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044C\u0442\u0435 \u044D\u0442\u0443 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0438\u044E \u0432 Git!\n    MARKDOWN\n\n    File.write(readme_path, readme_content)\n    logger.info(\"\u0421\u043E\u0437\u0434\u0430\u043D \u0444\u0430\u0439\u043B \u0441\u043F\u0440\u0430\u0432\u043A\u0438: \#{readme_path}\")\n\n    logger.info(\"\\n\u2705 \u0418\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430 \u0443\u0441\u043F\u0435\u0448\u043D\u043E\\n\")\n    logger.info('\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0435 \u0448\u0430\u0433\u0438:')\n    logger.info(\"1. \u041F\u043E\u043C\u0435\u0441\u0442\u0438\u0442\u0435 \u0432\u0430\u0448\u0438 \u0444\u0430\u0439\u043B\u044B \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432 \u0432 \#{secrets_path}/\")\n    logger.info(\"2. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 'keysloth push' \u0434\u043B\u044F \u043F\u0435\u0440\u0432\u043E\u0439 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432\")\n    logger.info('3. \u041F\u043E\u0434\u0435\u043B\u0438\u0442\u0435\u0441\u044C \u043F\u0430\u0440\u043E\u043B\u0435\u043C \u0448\u0438\u0444\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0441 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439')\n  rescue KeySloth::KeySlothError => e\n    logger.error(\"\u26D4 \u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438: \#{e.message}\")\n    exit 1\n  rescue StandardError => e\n    logger.error(\"\u26D4 \u041D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430: \#{e.message}\")\n    exit 1\n  end\nend\n"

#pull ⇒ Object

Since:

  • 0.1.0



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/keysloth/cli.rb', line 49

def pull
  setup_logger

  logger.info("\n=== Начинаем операцию получения секретов ===")

  begin
    KeySloth.pull(
      repo_url: options[:repo],
      branch: options[:branch],
      password: options[:password],
      local_path: options[:path],
      config_file: options[:config]
    )

    logger.info("✅ Операция получения секретов завершена успешно\n")
  rescue KeySloth::KeySlothError => e
    logger.error("⛔ Ошибка выполнения операции: #{e.message}")
    exit 1
  end
end

#push ⇒ Object

Since:

  • 0.1.0



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/keysloth/cli.rb', line 91

def push
  setup_logger

  logger.info("\n=== Начинаем операцию отправки секретов ===")

  begin
    KeySloth.push(
      repo_url: options[:repo],
      branch: options[:branch],
      password: options[:password],
      local_path: options[:path],
      config_file: options[:config],
      commit_message: options[:message]
    )

    logger.info("✅ Операция отправки секретов завершена успешно\n")
  rescue KeySloth::KeySlothError => e
    logger.error("⛔ Ошибка выполнения операции: #{e.message}")
    exit 1
  end
end

#restore(backup_path) ⇒ Object

Since:

  • 0.1.0



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/keysloth/cli.rb', line 194

def restore(backup_path)
  setup_logger

  logger.info("\n=== Восстанавливаем секреты из резервной копии ===")

  begin
    file_manager = FileManager.new(logger)
    config = Config.load(options[:config])
    resolved_path = options[:path] || config[:local_path] || Config::DEFAULT_CONFIG[:local_path]
    file_manager.restore_from_backup(backup_path, resolved_path)

    logger.info("✅ Восстановление завершено успешно\n")
  rescue KeySloth::KeySlothError => e
    logger.error("⛔ Ошибка восстановления: #{e.message}")
    exit 1
  end
end

#status ⇒ Object

Since:

  • 0.1.0



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/keysloth/cli.rb', line 123

def status
  setup_logger

  logger.info('=== Проверяем состояние локальных секретов ===')

  begin
    file_manager = FileManager.new(logger)
    # Выбор пути: CLI -> конфиг -> дефолт
    config = Config.load(options[:config])
    resolved_path = options[:path] || config[:local_path] || Config::DEFAULT_CONFIG[:local_path]

    unless file_manager.directory_exists?(resolved_path)
      logger.warn("Директория секретов не существует: #{resolved_path}")
      return
    end

    # Собираем файлы секретов
    secret_files = file_manager.collect_secret_files(resolved_path)

    if secret_files.empty?
      logger.info('Файлы секретов не найдены')
      return
    end

    logger.info("Найдено #{secret_files.size} файлов секретов:")

    secret_files.each do |file_path|
      size = File.size(file_path)
      relative_path = file_manager.get_relative_path(file_path, resolved_path)
      integrity = file_manager.verify_file_integrity(file_path) ? '✓' : '✗'

      logger.info("  #{integrity} #{relative_path} (#{size} байт)")
    end

    # Показываем информацию о backup'ах
    backups = file_manager.list_backups(resolved_path)
    if backups.any?
      logger.info("\nДоступные резервные копии:")
      backups.each do |backup_path|
        backup_time = File.basename(backup_path).match(/_(\d{8}_\d{6})$/)&.captures&.first
        formatted_time = begin
          if backup_time
            Time.strptime(backup_time, '%Y%m%d_%H%M%S').strftime('%Y-%m-%d %H:%M:%S')
          end
        rescue StandardError
          nil
        end

        if formatted_time
          logger.info("  #{File.basename(backup_path)} (#{formatted_time})")
        else
          logger.info("  #{File.basename(backup_path)}")
        end
      end
    else
      logger.info("\nРезервные копии не найдены")
    end
  rescue KeySloth::KeySlothError => e
    logger.error("⛔ Ошибка проверки состояния: #{e.message}")
    exit 1
  end
end

#validate ⇒ Object

Since:

  • 0.1.0



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/keysloth/cli.rb', line 223

def validate
  setup_logger

  logger.info("\n=== Проверяем целостность файлов секретов ===")

  begin
    file_manager = FileManager.new(logger)
    config = Config.load(options[:config])
    resolved_path = options[:path] || config[:local_path] || Config::DEFAULT_CONFIG[:local_path]

    unless file_manager.directory_exists?(resolved_path)
      logger.error("Директория секретов не существует: #{resolved_path}")
      exit 1
    end

    # Собираем файлы секретов
    secret_files = file_manager.collect_secret_files(resolved_path)

    if secret_files.empty?
      logger.warn('Файлы секретов не найдены')
      return
    end

    logger.info("Проверяем #{secret_files.size} файлов секретов...")

    valid_files = 0
    invalid_files = 0

    secret_files.each do |file_path|
      relative_path = file_manager.get_relative_path(file_path, resolved_path)

      if file_manager.verify_file_integrity(file_path)
        logger.info("  ✓ #{relative_path} - файл корректен")
        valid_files += 1
      else
        logger.error("  ✗ #{relative_path} - файл поврежден или недоступен")
        invalid_files += 1
      end
    end

    # Итоговый отчет
    logger.info("\n=== Результаты проверки ===")
    logger.info("Корректных файлов: #{valid_files}")
    logger.info("Поврежденных файлов: #{invalid_files}")

    if invalid_files.positive?
      logger.error("⛔ Обнаружены поврежденные файлы! Рекомендуется восстановление из backup'а.")
      exit 1
    else
      logger.info('Все файлы секретов прошли проверку целостности.')
      logger.info("✅ Проверка целостности завершена успешно\n")
    end
  rescue KeySloth::KeySlothError => e
    logger.error("⛔ Ошибка проверки целостности: #{e.message}")
    exit 1
  end
end

#version ⇒ Object

Since:

  • 0.1.0



401
402
403
# File 'lib/keysloth/cli.rb', line 401

def version
  puts "KeySloth версия #{KeySloth::VERSION}"
end