From 92faeb2c1b01e2f5f8bb8a9915209efc92ffa5d4 Mon Sep 17 00:00:00 2001 From: Glenn Date: Sat, 9 Jul 2022 16:19:44 +0200 Subject: [PATCH] Initial import --- .gitignore | 2 + Dockerfile | 37 +++++++++ Makefile | 15 ++++ README.md | 4 + arkisto.sample.yml | 22 +++++ shard.lock | 6 ++ shard.yml | 25 ++++++ spec/data/openstack.sample.yml | 51 ++++++++++++ src/arkistoctl/main.cr | 141 +++++++++++++++++++++++++++++++++ 9 files changed, 303 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 arkisto.sample.yml create mode 100644 shard.lock create mode 100644 shard.yml create mode 100644 spec/data/openstack.sample.yml create mode 100644 src/arkistoctl/main.cr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3933460 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +lib +bin diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c0a6cfa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ + +## Stage 1 : build binary +FROM crystallang/crystal:1.4.1 AS builder + +COPY . /app +WORKDIR /app +RUN shards build + +## Stage 2 : include binary into final image +FROM python:3.7-bullseye AS runner + +RUN apt-get update \ + && apt-get -yq install \ + libevent-core-2.1-7 \ + gettext \ + jq \ + python3-aodhclient \ + python3-barbicanclient \ + python3-ceilometerclient \ + python3-cinderclient \ + python3-cloudkittyclient \ + python3-designateclient \ + python3-gnocchiclient \ + python3-octaviaclient \ + python3-osc-placement \ + python3-openstackclient \ + python3-pankoclient \ + zip \ + && rm -rf /var/lib/apt/lists/* + +# RUN wget https://github.com/sapcc/cyclone/releases/download/v0.1.28/cyclone \ +# -O /usr/bin/cyclone \ +# && chmod +x /usr/bin/cyclone + +COPY --from=builder /app/bin/arkisto /usr/bin/arkisto + +CMD /bin/bash diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..10f496f --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ + + +build: + shards build + +docker: docker-build docker-test docker-push + +docker-build: + docker build -t glenux/openstack-arkisto . + +docker-push: + docker push glenux/openstack-arkisto + +docker-test: + docker run glenux/openstack-arkisto arkisto --version diff --git a/README.md b/README.md new file mode 100644 index 0000000..84acc41 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Arkisto + +A simple tool to automate openstack snapshots + diff --git a/arkisto.sample.yml b/arkisto.sample.yml new file mode 100644 index 0000000..fc43634 --- /dev/null +++ b/arkisto.sample.yml @@ -0,0 +1,22 @@ +--- +version: "1" + +retention: + days: 8 + cycle: true + max_items: 2 + +targets: + - name: smart-app + volume_id: x + snapshot_prefix: y + - name: smart-db + volume_id: x + snapshot_prefix: y + - name: short-db + volume_id: x2 + snapshot_prefix: y + - name: collector-db + volume_id: x2 + snapshot_prefix: y +# diff --git a/shard.lock b/shard.lock new file mode 100644 index 0000000..514a6ac --- /dev/null +++ b/shard.lock @@ -0,0 +1,6 @@ +version: 2.0 +shards: + tablo: + git: https://github.com/hutou/tablo.git + version: 0.10.1 + diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..0c75e9d --- /dev/null +++ b/shard.yml @@ -0,0 +1,25 @@ +--- +name: arkisto +version: 0.1.0 + +authors: + - Glenn Y. Rolland + +description: | + Simple and stupid utility to cycle openstack volume backups/snapshots + +targets: + arkisto: + main: src/arkistoctl/main.cr + +dependencies: + # Text tables + tablo: + github: hutou/tablo + + +# development_dependencies: +# webmock: +# github: manastech/webmock.cr + +license: MIT diff --git a/spec/data/openstack.sample.yml b/spec/data/openstack.sample.yml new file mode 100644 index 0000000..ea93645 --- /dev/null +++ b/spec/data/openstack.sample.yml @@ -0,0 +1,51 @@ +- Attached to: [] + ID: 7b715476-5397-48de-87ac-5a63170fff71 + Name: ovh-managed-kubernetes-e6j07m-pvc-fc264e28-94e3-43c5-afe1-3c99b6d95bfb + Size: 1 + Status: available +- Attached to: [] + ID: c9d05e3e-c668-47fd-8bd6-0cedc9dc02c5 + Name: ovh-managed-kubernetes-e6j07m-pvc-44bca100-2f8c-4be2-9491-d2254a42287f + Size: 2000 + Status: available +- Attached to: [] + ID: 8c87c196-1dd6-4190-bff5-00b543965093 + Name: ovh-managed-kubernetes-e6j07m-pvc-9fad19b4-b32c-452a-9893-524fcdc3a4b1 + Size: 50 + Status: available +- Attached to: + - attached_at: '2022-06-26T19:03:31.000000' + attachment_id: 503f8552-e2f9-4aff-a489-8382bbed3883 + device: /dev/sdb + host_name: null + id: c9b11fcb-a6a4-4387-b55b-34121f97dac4 + server_id: 7ee31946-16ab-4759-be33-6499a339f929 + volume_id: c9b11fcb-a6a4-4387-b55b-34121f97dac4 + ID: c9b11fcb-a6a4-4387-b55b-34121f97dac4 + Name: ovh-managed-kubernetes-e6j07m-pvc-13dcac68-71c9-4794-b0ab-d48e76d843ef + Size: 5800 + Status: in-use +- Attached to: + - attached_at: '2022-06-21T12:47:29.000000' + attachment_id: 569b7110-74e1-4f87-b92d-ab5e1a2a473c + device: /dev/sdb + host_name: null + id: de7b118f-413b-4962-a028-a742df4bc4fc + server_id: e1753464-9328-4eea-ae98-21c332e94dc5 + volume_id: de7b118f-413b-4962-a028-a742df4bc4fc + ID: de7b118f-413b-4962-a028-a742df4bc4fc + Name: ovh-managed-kubernetes-e6j07m-pvc-dfbf1e3a-7ed6-4a90-a4e0-4b5516f88051 + Size: 50 + Status: in-use +- Attached to: + - attached_at: '2022-06-27T17:31:53.000000' + attachment_id: 845a7ff2-ad54-4040-b69b-fce7fdfdece2 + device: /dev/sdb + host_name: null + id: 6d4492e5-6b38-4ad3-b9bf-27ba86e2506b + server_id: 37220c33-19e7-45cc-9c6a-ddf957be3c76 + volume_id: 6d4492e5-6b38-4ad3-b9bf-27ba86e2506b + ID: 6d4492e5-6b38-4ad3-b9bf-27ba86e2506b + Name: ovh-managed-kubernetes-e6j07m-pvc-7bf995bf-ac82-4f42-94ca-78cd9c78959d + Size: 1 + Status: in-use diff --git a/src/arkistoctl/main.cr b/src/arkistoctl/main.cr new file mode 100644 index 0000000..5bf1cac --- /dev/null +++ b/src/arkistoctl/main.cr @@ -0,0 +1,141 @@ +require "yaml" +require "option_parser" +require "colorize" +# require "xdg_basedir" +# require "completion" +require "log" + +require "../lib/actions" +require "../lib/types" +require "../lib/models" +require "../lib/version" + + +module Arkisto + class CtlCli + property config : ConfigModel? + property options : GlobalOptions? + + def initialize + # @config = nil + @options = nil + end + + def self.parse_options(args) : GlobalOptions + # default values + action = NoneAction + config_file = "arkisto.yml" + verbose = true + dry_run = false + + # parse + OptionParser.parse(args) do |parser| + parser.banner = "Usage: #{Version::PROGRAM_ARKISTOCTL} [options] [commands] [arguments]" + + parser.separator + parser.separator "Options" + + parser.on "-c CONFIG_FILE", "--config=CONFIG_FILE", "Use the following config file" do |file| + config_file = file + end + + parser.on "-v", "--verbose", "Be verbose" do + verbose = !verbose + end + + parser.on "--dry-run", "Dry run (no real openstack command is executed" do + dry_run = true + end + + parser.on "--version", "Show version" do + puts "version #{Version::VERSION}" + exit 0 + end + + parser.on "-h", "--help", "Show this help" do + puts parser + exit 0 + end + + parser.on "--completion", "Provide autocompletion for bash" do + # nothing here + end + + parser.on "plan", "Display backup plan" do + action = PlanAction + end + + parser.on "apply", "Apply backup plan" do + action = ApplyAction + end + + parser.separator + + parser.missing_option do |flag| + STDERR.puts parser + STDERR.puts "ERROR: #{flag} requires an argument.".colorize(:red) + exit(1) + end + + parser.invalid_option do |flag| + STDERR.puts parser + STDERR.puts "ERROR: #{flag} is not a valid option.".colorize(:red) + exit(1) + end + + # complete_with Version::PROGRAM_ARKISTOCTL, parser + end + + return { + action: action, + config_file: config_file, + dry_run: dry_run, + verbose: verbose, + }.as(GlobalOptions) + end + + def self.parse_config(options) + config_file = options[:config_file] + puts "Loading configuration... #{config_file}".colorize(:yellow) if options[:verbose] + + if ! File.exists? config_file + STDERR.puts "ERROR: Unable to read configuration file '#{config_file}'".colorize(:red) + exit 1 + end + + yaml_str = File.read(config_file) + config = ConfigModel.from_yaml(yaml_str) + + if config.nil? + STDERR.puts "ERROR: Invalid YAML content in '#{config_file}'" + exit 1 + end + + return config + end + + def self.run(args) + app = CtlCli.new + options = CtlCli.parse_options(args) + config = CtlCli.parse_config(options) + app.options = options + app.config = config + + action = ActionFactory.build( + options[:action], + config, + { + dry_run: options[:dry_run], + verbose: options[:verbose] + } + ) + action.perform + + rescue ex : YAML::ParseException + STDERR.puts "ERROR: #{ex.message}".colorize(:red) + end + end +end + +Arkisto::CtlCli.run(ARGV) +