arkana 0.1.0 → 1.0.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.
- checksums.yaml +4 -4
- data/bin/arkana +6 -2
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/arkana/config_parser.rb +20 -0
- data/lib/arkana/encoder.rb +50 -0
- data/lib/arkana/helpers/dotenv_helper.rb +19 -0
- data/lib/arkana/helpers/string.rb +22 -0
- data/lib/arkana/helpers/swift_template_helper.rb +12 -0
- data/lib/arkana/helpers/ui.rb +33 -0
- data/lib/arkana/models/arguments.rb +32 -0
- data/lib/arkana/models/config.rb +62 -0
- data/lib/arkana/models/salt.rb +20 -0
- data/lib/arkana/models/secret.rb +33 -0
- data/lib/arkana/models/template_arguments.rb +48 -0
- data/lib/arkana/models/type.rb +16 -0
- data/lib/arkana/salt_generator.rb +18 -0
- data/lib/arkana/swift_code_generator.rb +56 -0
- data/lib/arkana/templates/arkana.podspec.erb +15 -0
- data/lib/arkana/templates/arkana.swift.erb +52 -0
- data/lib/arkana/templates/arkana_protocol.swift.erb +15 -0
- data/lib/arkana/templates/arkana_tests.swift.erb +41 -0
- data/lib/arkana/templates/interfaces.podspec.erb +15 -0
- data/lib/arkana/templates/interfaces_package.swift.erb +27 -0
- data/lib/arkana/templates/interfaces_readme.erb +3 -0
- data/lib/arkana/templates/package.swift.erb +36 -0
- data/lib/arkana/templates/readme.erb +4 -0
- data/lib/arkana/version.rb +5 -0
- data/lib/arkana.rb +50 -0
- metadata +78 -7
- data/LICENSE +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17be699093098399339947f5a8d0df3d5ff20fdb8186cfc9bd92fa1c3aa330d2
|
4
|
+
data.tar.gz: e7b919f59bf5a491bfa9d4f166753f374a94a33a5023275e5d6d35549a0d1e58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fde3754bfdb500ef9607827f14927ab0e0d678f70ee30398b238d56ba07729d27c967ab890c0b933432898cf8577e6ed3e642ed1ec442a85020c11d54f6d0b0f
|
7
|
+
data.tar.gz: 83880b13c6eeea494d7b85d5a73fe01cba9834156eb5d5d3a51d5cc9f7030a069e312eb98d3ab017861c67b818fcc9d1abe83de3bfb0850e3ddb520df4e80e07
|
data/bin/arkana
CHANGED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "arkana"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require_relative "models/config"
|
5
|
+
require_relative "helpers/ui"
|
6
|
+
|
7
|
+
# The config parser is responsible for parsing user CLI arguments, reading the config file and returning a `Config` object.
|
8
|
+
module ConfigParser
|
9
|
+
# Parses the config file defined by the user (if any), returning a Config object.
|
10
|
+
#
|
11
|
+
# @return [Config] the config parsed from the user's options.
|
12
|
+
def self.parse(arguments)
|
13
|
+
yaml = YAML.load_file(arguments.config_filepath)
|
14
|
+
config = Config.new(yaml)
|
15
|
+
config.current_flavor = arguments.flavor
|
16
|
+
config.dotenv_filepath = arguments.dotenv_filepath
|
17
|
+
UI.warn("Dotenv file was specified but couldn't be found at '#{config.dotenv_filepath}'") if config.dotenv_filepath && !File.exist?(config.dotenv_filepath)
|
18
|
+
config
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "arkana/helpers/string"
|
4
|
+
|
5
|
+
# The encoder is responsible for finding the env vars for given keys, encoding them, and creating Secrets based on the generated data.
|
6
|
+
module Encoder
|
7
|
+
# Fetches values of each key from ENV, and encodes them using the given salt.
|
8
|
+
#
|
9
|
+
# @return [Secret[]] an array of Secret objects, which contain their keys and encoded values.
|
10
|
+
def self.encode!(keys:, salt:, current_flavor:, environments:)
|
11
|
+
keys.map do |key|
|
12
|
+
secret = find_secret!(key: key, current_flavor: current_flavor)
|
13
|
+
encoded_value = encode(secret, salt.raw)
|
14
|
+
secret_type = Type.new(string_value: secret)
|
15
|
+
protocol_key = protocol_key(key: key, environments: environments)
|
16
|
+
Secret.new(key: key, protocol_key: protocol_key, encoded_value: encoded_value, type: secret_type)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Encodes the given string, using the given cipher.
|
21
|
+
def self.encode(string, cipher)
|
22
|
+
bytes = string.encode("utf-8")
|
23
|
+
result = []
|
24
|
+
(0...bytes.length).each do |index|
|
25
|
+
byte = bytes[index].ord # Convert to its codepoint representation
|
26
|
+
result << (byte ^ cipher[index % cipher.length]) # XOR operation with a value of the cipher array.
|
27
|
+
end
|
28
|
+
|
29
|
+
encoded_key = []
|
30
|
+
result.each do |element|
|
31
|
+
# Warning: this might be specific to Swift implementation. When generating code for other languages, beware.
|
32
|
+
encoded_key << format("%#x", element) # Format the binary number to "0xAB" format.
|
33
|
+
end
|
34
|
+
|
35
|
+
encoded_key.join(", ")
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_secret!(key:, current_flavor:)
|
39
|
+
flavor_key = "#{current_flavor.capitalize_first_letter}#{key}" if current_flavor
|
40
|
+
secret = ENV[flavor_key] if flavor_key
|
41
|
+
secret ||= ENV[key] || raise("Secret '#{flavor_key || key}' was declared but couldn't be found in the environment variables nor in the specified dotenv file.")
|
42
|
+
secret
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.protocol_key(key:, environments:)
|
46
|
+
environments.filter_map do |env|
|
47
|
+
key.delete_suffix(env) if key.end_with?(env)
|
48
|
+
end.first || key
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dotenv"
|
4
|
+
|
5
|
+
# This helper is a mere utility used to facilitate and orchestrate the loading of multiple Dotenv files.
|
6
|
+
module DotenvHelper
|
7
|
+
# Loads the appropriate dotenv file(s).
|
8
|
+
def self.load(config)
|
9
|
+
Dotenv.load(config.dotenv_filepath) if config.dotenv_filepath
|
10
|
+
# Must be loaded after loading the `config.dotenv_filepath` so they override each other in the right order
|
11
|
+
Dotenv.load(flavor_dotenv_filepath(config)) if config.current_flavor
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.flavor_dotenv_filepath(config)
|
15
|
+
dotenv_dirname = File.dirname(config.dotenv_filepath)
|
16
|
+
flavor_dotenv_filename = ".env.#{config.current_flavor.downcase}"
|
17
|
+
File.join(dotenv_dirname, flavor_dotenv_filename)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# String extensions and utilities.
|
4
|
+
class String
|
5
|
+
# Returns a string converted from an assumed PascalCase, to camelCase.
|
6
|
+
def camel_case
|
7
|
+
return self if empty?
|
8
|
+
|
9
|
+
copy = dup
|
10
|
+
copy[0] = copy[0].downcase
|
11
|
+
copy
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a string with its first character capitalized.
|
15
|
+
def capitalize_first_letter
|
16
|
+
return self if empty?
|
17
|
+
|
18
|
+
copy = dup
|
19
|
+
copy[0] = copy[0].upcase
|
20
|
+
copy
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Utilities to reduce the amount of boilerplate code in `.swift.erb` template files.
|
4
|
+
module SwiftTemplateHelper
|
5
|
+
def self.swift_type(type)
|
6
|
+
case type
|
7
|
+
when :string then "String"
|
8
|
+
when :boolean then "Bool"
|
9
|
+
else raise "Unknown variable type '#{type}' received.'"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "colorize"
|
5
|
+
|
6
|
+
# Contains utilities related to display information to the user on the Terminal (the user's interface).
|
7
|
+
module UI
|
8
|
+
# Logs the message in red color and raise an error with the given message.
|
9
|
+
def self.crash(message)
|
10
|
+
logger.fatal(message.red)
|
11
|
+
raise message
|
12
|
+
end
|
13
|
+
|
14
|
+
# Logs the message in cyan color.
|
15
|
+
def self.debug(message)
|
16
|
+
logger.debug(message.cyan)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Logs the message in green color.
|
20
|
+
def self.success(message)
|
21
|
+
logger.info(message.green)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Logs the message in yellow color.
|
25
|
+
def self.warn(message)
|
26
|
+
logger.warn(message.yellow)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Logger used to log all the messages.
|
30
|
+
def self.logger
|
31
|
+
@logger ||= Logger.new($stdout)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
# Model that parses and documents the CLI options, using `OptionParser`.
|
6
|
+
class Arguments
|
7
|
+
# @returns [string]
|
8
|
+
attr_reader :config_filepath
|
9
|
+
# @returns [string]
|
10
|
+
attr_reader :dotenv_filepath
|
11
|
+
# @returns [string]
|
12
|
+
attr_reader :flavor
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
# Default values
|
16
|
+
@config_filepath = ".arkana.yml"
|
17
|
+
@dotenv_filepath = ".env" if File.exist?(".env")
|
18
|
+
@flavor = nil
|
19
|
+
|
20
|
+
OptionParser.new do |opt|
|
21
|
+
opt.on("-c", "--config-filepath /path/to/your/.arkana.yml", "Path to your config file. Defaults to '.arkana.yml'") do |o|
|
22
|
+
@config_filepath = o
|
23
|
+
end
|
24
|
+
opt.on("-e", "--dotenv-filepath /path/to/your/.env", "Path to your dotenv file. Defaults to '.env' if one exists.") do |o|
|
25
|
+
@dotenv_filepath = o
|
26
|
+
end
|
27
|
+
opt.on("-f", "--flavor FrostedFlakes", "Flavors are useful, for instance, when generating secrets for white-label projects. See the README for more information") do |o|
|
28
|
+
@flavor = o
|
29
|
+
end
|
30
|
+
end.parse!
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Model used to hold all the configuration set up by the user (both from CLI arguments and from the config file).
|
4
|
+
class Config
|
5
|
+
# @returns [string[]]
|
6
|
+
attr_reader :environments
|
7
|
+
# @returns [string[]]
|
8
|
+
attr_reader :global_secrets
|
9
|
+
# @returns [string[]]
|
10
|
+
attr_reader :environment_secrets
|
11
|
+
# @returns [string]
|
12
|
+
attr_reader :import_name
|
13
|
+
# @returns [string]
|
14
|
+
attr_reader :namespace
|
15
|
+
# @returns [string]
|
16
|
+
attr_reader :pod_name
|
17
|
+
# @returns [string]
|
18
|
+
attr_reader :result_path
|
19
|
+
# @returns [string[]]
|
20
|
+
attr_reader :flavors
|
21
|
+
# @returns [string]
|
22
|
+
attr_reader :swift_declaration_strategy
|
23
|
+
# @returns [boolean]
|
24
|
+
attr_reader :should_generate_unit_tests
|
25
|
+
# @returns [string]
|
26
|
+
attr_reader :package_manager
|
27
|
+
|
28
|
+
# @returns [string]
|
29
|
+
attr_accessor :current_flavor
|
30
|
+
# @returns [string]
|
31
|
+
attr_accessor :dotenv_filepath
|
32
|
+
|
33
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
34
|
+
def initialize(yaml)
|
35
|
+
@environments = (yaml["environments"] || []).map(&:capitalize)
|
36
|
+
@environment_secrets = yaml["environment_secrets"] || []
|
37
|
+
@global_secrets = yaml["global_secrets"] || []
|
38
|
+
default_name = "ArkanaKeys"
|
39
|
+
@namespace = yaml["namespace"] || default_name
|
40
|
+
@import_name = yaml["import_name"] || default_name
|
41
|
+
@pod_name = yaml["pod_name"] || default_name
|
42
|
+
@result_path = yaml["result_path"] || default_name
|
43
|
+
@flavors = yaml["flavors"] || []
|
44
|
+
@swift_declaration_strategy = yaml["swift_declaration_strategy"] || "let"
|
45
|
+
@should_generate_unit_tests = yaml["should_generate_unit_tests"]
|
46
|
+
@should_generate_unit_tests = true if @should_generate_unit_tests.nil?
|
47
|
+
@package_manager = yaml["package_manager"] || "spm"
|
48
|
+
end
|
49
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
50
|
+
|
51
|
+
# TODO: Consider renaming this and environment_secrets, cuz they're confusing.
|
52
|
+
def environment_keys
|
53
|
+
result = environment_secrets.map do |k|
|
54
|
+
environments.map { |e| k + e }
|
55
|
+
end
|
56
|
+
result.flatten
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_keys
|
60
|
+
global_secrets + environment_keys
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "type"
|
4
|
+
|
5
|
+
# Model used to hold the metadata for the salt bytes.
|
6
|
+
class Salt
|
7
|
+
attr_reader :raw
|
8
|
+
# Salt, formatted as a string of comma-separated hexadecimal numbers, e.g. "0x94, 0x11, 0x1b, 0x6, 0x49, 0, 0xa7"
|
9
|
+
attr_reader :formatted
|
10
|
+
|
11
|
+
def initialize(raw:)
|
12
|
+
@raw = raw
|
13
|
+
formatted_salt = []
|
14
|
+
raw.each do |element|
|
15
|
+
# Warning: this might be specific to Swift implementation. When generating code for other languages, beware.
|
16
|
+
formatted_salt << format("%#x", element)
|
17
|
+
end
|
18
|
+
@formatted = formatted_salt.join(", ")
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "type"
|
4
|
+
|
5
|
+
# Model used to hold the metadata for a secret, such as its env var key, its protocol key, its type, and encoded value.
|
6
|
+
class Secret
|
7
|
+
# @returns [string]
|
8
|
+
attr_accessor :key
|
9
|
+
# The same string as the key, except without the "environment" suffix.
|
10
|
+
# Used in the declaration of the protocol that defines the environment secrets.
|
11
|
+
# @returns [string]
|
12
|
+
attr_accessor :protocol_key
|
13
|
+
# Encoded value, formatted as a string of comma-separated hexadecimal numbers, e.g. "0x94, 0x11, 0x1b, 0x6, 0x49, 0, 0xa7"
|
14
|
+
# @returns [string]
|
15
|
+
attr_reader :encoded_value
|
16
|
+
# The type of the variable, as a language-agnostic enum.
|
17
|
+
# @returns [Type]
|
18
|
+
attr_reader :type
|
19
|
+
|
20
|
+
def initialize(key:, protocol_key:, encoded_value:, type:)
|
21
|
+
@key = key
|
22
|
+
@protocol_key = protocol_key
|
23
|
+
@encoded_value = encoded_value
|
24
|
+
@type = type
|
25
|
+
end
|
26
|
+
|
27
|
+
def environment
|
28
|
+
return :global if key == protocol_key
|
29
|
+
return key.delete_prefix(protocol_key) if key.start_with?(protocol_key)
|
30
|
+
|
31
|
+
raise("Precondition failure: the protocol_key '#{protocol_key}' is not the same as the key '#{key}' nor is its prefix. This state shouldn't be possible.")
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "arkana/models/secret"
|
4
|
+
|
5
|
+
# A class that defines template arguments in a language-agnostic form.
|
6
|
+
class TemplateArguments
|
7
|
+
# Generates template arguments for the given secrets and config specifications.
|
8
|
+
def initialize(environment_secrets:, global_secrets:, config:, salt:)
|
9
|
+
# The environments declared in the config file.
|
10
|
+
@environments = config.environments
|
11
|
+
# The salt used to encode all the secrets.
|
12
|
+
@salt = salt
|
13
|
+
# The encoded environment-specific secrets.
|
14
|
+
@environment_secrets = environment_secrets
|
15
|
+
# The encoded global secrets.
|
16
|
+
@global_secrets = global_secrets
|
17
|
+
# Name of the import statements (or the interfaces' prefixes).
|
18
|
+
@import_name = config.import_name
|
19
|
+
# Name of the pod that should be generated.
|
20
|
+
@pod_name = config.pod_name
|
21
|
+
# The top level namespace in which the keys will be generated. Often an enum.
|
22
|
+
@namespace = config.namespace
|
23
|
+
# The property declaration strategy declared in the config file.
|
24
|
+
@swift_declaration_strategy = config.swift_declaration_strategy
|
25
|
+
# Whether unit tests should be generated.
|
26
|
+
@should_generate_unit_tests = config.should_generate_unit_tests
|
27
|
+
end
|
28
|
+
|
29
|
+
def environment_protocol_secrets(environment)
|
30
|
+
@environment_secrets.select do |secret|
|
31
|
+
secret.environment == environment
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generates a new test secret for a given key, using the salt stored.
|
36
|
+
def generate_test_secret(key:)
|
37
|
+
# Yes, we encode the key as the value because this is just for testing purposes
|
38
|
+
encoded_value = Encoder.encode(key, @salt.raw)
|
39
|
+
Secret.new(key: key, protocol_key: key, encoded_value: encoded_value, type: :string)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Expose private `binding` method.
|
43
|
+
# rubocop:disable Naming/AccessorMethodName
|
44
|
+
def get_binding
|
45
|
+
binding
|
46
|
+
end
|
47
|
+
# rubocop:enable Naming/AccessorMethodName
|
48
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A namespace that defines language-agnostic variable types.
|
4
|
+
module Type
|
5
|
+
STRING = :string
|
6
|
+
BOOLEAN = :boolean
|
7
|
+
|
8
|
+
def self.new(string_value:)
|
9
|
+
case string_value
|
10
|
+
when "true", "false"
|
11
|
+
BOOLEAN
|
12
|
+
else
|
13
|
+
STRING
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require_relative "models/salt"
|
5
|
+
|
6
|
+
# Responsible for generating the salt.
|
7
|
+
module SaltGenerator
|
8
|
+
SALT_LENGTH = 64
|
9
|
+
|
10
|
+
# Generates and returns a new Salt object.
|
11
|
+
#
|
12
|
+
# @return [Salt] the salt that was just generated.
|
13
|
+
def self.generate
|
14
|
+
chars = SecureRandom.random_bytes(SALT_LENGTH).chars # Convert array of random bytes to array of characters
|
15
|
+
codepoints = chars.map(&:ord) # Convert each character to its codepoint representation
|
16
|
+
Salt.new(raw: codepoints)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require_relative "helpers/string"
|
5
|
+
|
6
|
+
# Responsible for generating Swift source and test files.
|
7
|
+
module SwiftCodeGenerator
|
8
|
+
# Generates Swift code and test files for the given template arguments.
|
9
|
+
def self.generate(template_arguments:, config:)
|
10
|
+
interface_swift_package_dir = File.join(config.result_path, "#{config.import_name}Interfaces")
|
11
|
+
set_up_interfaces_swift_package(interface_swift_package_dir, template_arguments, config)
|
12
|
+
|
13
|
+
swift_package_dir = File.join(config.result_path, config.import_name)
|
14
|
+
set_up_swift_package(swift_package_dir, template_arguments, config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.set_up_interfaces_swift_package(path, template_arguments, config)
|
18
|
+
dirname = File.dirname(__FILE__)
|
19
|
+
readme_template = File.read("#{dirname}/templates/interfaces_readme.erb")
|
20
|
+
package_template = File.read("#{dirname}/templates/interfaces_package.swift.erb")
|
21
|
+
podspec_template = File.read("#{dirname}/templates/interfaces.podspec.erb")
|
22
|
+
sources_dir = File.join(path, "Sources")
|
23
|
+
source_template = File.read("#{dirname}/templates/arkana_protocol.swift.erb")
|
24
|
+
FileUtils.mkdir_p(path)
|
25
|
+
FileUtils.mkdir_p(sources_dir)
|
26
|
+
render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}Interfaces.podspec")) if config.package_manager == "cocoapods"
|
27
|
+
render(readme_template, template_arguments, File.join(path, "README.md"))
|
28
|
+
render(package_template, template_arguments, File.join(path, "Package.swift"))
|
29
|
+
render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}Interfaces.swift"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.set_up_swift_package(path, template_arguments, config)
|
33
|
+
dirname = File.dirname(__FILE__)
|
34
|
+
readme_template = File.read("#{dirname}/templates/readme.erb")
|
35
|
+
package_template = File.read("#{dirname}/templates/package.swift.erb")
|
36
|
+
sources_dir = File.join(path, "Sources")
|
37
|
+
source_template = File.read("#{dirname}/templates/arkana.swift.erb")
|
38
|
+
tests_dir = File.join(path, "Tests") if config.should_generate_unit_tests
|
39
|
+
tests_template = File.read("#{dirname}/templates/arkana_tests.swift.erb")
|
40
|
+
podspec_template = File.read("#{dirname}/templates/arkana.podspec.erb")
|
41
|
+
FileUtils.mkdir_p(path)
|
42
|
+
FileUtils.mkdir_p(sources_dir)
|
43
|
+
FileUtils.mkdir_p(tests_dir) if config.should_generate_unit_tests
|
44
|
+
render(readme_template, template_arguments, File.join(path, "README.md"))
|
45
|
+
render(package_template, template_arguments, File.join(path, "Package.swift"))
|
46
|
+
render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}.podspec")) if config.package_manager == "cocoapods"
|
47
|
+
render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}.swift"))
|
48
|
+
render(tests_template, template_arguments, File.join(tests_dir, "#{config.import_name}Tests.swift")) if config.should_generate_unit_tests
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.render(template, template_arguments, destination_file)
|
52
|
+
renderer = ERB.new(template, trim_mode: ">") # Don't automatically add newlines at the end of each template tag
|
53
|
+
result = renderer.result(template_arguments.get_binding)
|
54
|
+
File.write(destination_file, result)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was autogenerated by Arkana. Do not attempt to modify it.
|
4
|
+
|
5
|
+
Pod::Spec.new do |spec|
|
6
|
+
spec.name = '<%= @pod_name %>'
|
7
|
+
spec.summary = 'Contains env vars and secrets. Autogenerated by Arkana.'
|
8
|
+
spec.homepage = 'https://github.com/rogerluan/arkana'
|
9
|
+
spec.version = '1.0.0'
|
10
|
+
spec.authors = { 'Arkana' => 'https://github.com/rogerluan/arkana' }
|
11
|
+
spec.source = { path: "./" }
|
12
|
+
spec.source_files = 'Sources/**/*.swift'
|
13
|
+
spec.ios.deployment_target = '11.0'
|
14
|
+
spec.swift_version = '5.0'
|
15
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<% require 'arkana/helpers/string' %>
|
2
|
+
<% require 'arkana/helpers/swift_template_helper' %>
|
3
|
+
<% # TODO: Sort these import statements alphabetically %>
|
4
|
+
import Foundation
|
5
|
+
import <%= @import_name %>Interfaces
|
6
|
+
|
7
|
+
public enum <%= @namespace %> {
|
8
|
+
fileprivate static let salt: [UInt8] = [
|
9
|
+
<%= @salt.formatted %>
|
10
|
+
|
11
|
+
]
|
12
|
+
|
13
|
+
static func decode(encoded: [UInt8], cipher: [UInt8]) -> String {
|
14
|
+
return String(decoding: encoded.enumerated().map { (offset, element) in
|
15
|
+
element ^ cipher[offset % cipher.count]
|
16
|
+
}, as: UTF8.self)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
public extension <%= @namespace %> {
|
21
|
+
struct Global: <%= @import_name %>GlobalProtocol {
|
22
|
+
public init() {}
|
23
|
+
<% for secret in @global_secrets %>
|
24
|
+
|
25
|
+
public <%= @swift_declaration_strategy %> <%= secret.key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
|
26
|
+
let encoded: [UInt8] = [
|
27
|
+
<%= secret.encoded_value %>
|
28
|
+
|
29
|
+
]
|
30
|
+
return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
|
31
|
+
}()
|
32
|
+
<% end %>
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
<% for environment in @environments %>
|
37
|
+
public extension <%= @namespace %> {
|
38
|
+
struct <%= environment %>: <%= @import_name %>EnvironmentProtocol {
|
39
|
+
public init() {}
|
40
|
+
<% for secret in environment_protocol_secrets(environment) %>
|
41
|
+
|
42
|
+
public <%= @swift_declaration_strategy %> <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
|
43
|
+
let encoded: [UInt8] = [
|
44
|
+
<%= secret.encoded_value %>
|
45
|
+
|
46
|
+
]
|
47
|
+
return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
|
48
|
+
}()
|
49
|
+
<% end %>
|
50
|
+
}
|
51
|
+
}
|
52
|
+
<% end %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% require 'arkana/helpers/string' %>
|
2
|
+
<% require 'arkana/helpers/swift_template_helper' %>
|
3
|
+
import Foundation
|
4
|
+
|
5
|
+
public protocol <%= @namespace %>GlobalProtocol {
|
6
|
+
<% for secret in @global_secrets %>
|
7
|
+
var <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> { get }
|
8
|
+
<% end %>
|
9
|
+
}
|
10
|
+
|
11
|
+
public protocol <%= @namespace %>EnvironmentProtocol {
|
12
|
+
<% for secret in @environment_secrets.uniq(&:protocol_key) %>
|
13
|
+
var <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> { get }
|
14
|
+
<% end %>
|
15
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import Foundation
|
2
|
+
import XCTest
|
3
|
+
@testable import <%= @import_name %>
|
4
|
+
|
5
|
+
|
6
|
+
final class <%= @namespace %>Tests: XCTestCase {
|
7
|
+
private var salt: [UInt8] = [
|
8
|
+
<%= @salt.formatted %>
|
9
|
+
|
10
|
+
]
|
11
|
+
|
12
|
+
func test_decodeRandomHexKey_shouldDecode() {
|
13
|
+
<% hex_key = SecureRandom.hex(64) %>
|
14
|
+
<% secret = generate_test_secret(key: hex_key) %>
|
15
|
+
let encoded: [UInt8] = [
|
16
|
+
<%= secret.encoded_value %>
|
17
|
+
|
18
|
+
]
|
19
|
+
XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= hex_key %>")
|
20
|
+
}
|
21
|
+
|
22
|
+
func test_decodeRandomBase64Key_shouldDecode() {
|
23
|
+
<% base64_key = SecureRandom.base64(64) %>
|
24
|
+
<% secret = generate_test_secret(key: base64_key) %>
|
25
|
+
let encoded: [UInt8] = [
|
26
|
+
<%= secret.encoded_value %>
|
27
|
+
|
28
|
+
]
|
29
|
+
XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= base64_key %>")
|
30
|
+
}
|
31
|
+
|
32
|
+
func test_decodeUUIDKey_shouldDecode() {
|
33
|
+
<% uuid_key = SecureRandom.uuid %>
|
34
|
+
<% secret = generate_test_secret(key: uuid_key) %>
|
35
|
+
let encoded: [UInt8] = [
|
36
|
+
<%= secret.encoded_value %>
|
37
|
+
|
38
|
+
]
|
39
|
+
XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= uuid_key %>")
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was autogenerated by Arkana. Do not attempt to modify it.
|
4
|
+
|
5
|
+
Pod::Spec.new do |spec|
|
6
|
+
spec.name = '<%= @pod_name %>Interfaces'
|
7
|
+
spec.summary = 'Contains env vars and secrets. Autogenerated by Arkana.'
|
8
|
+
spec.homepage = 'https://github.com/rogerluan/arkana'
|
9
|
+
spec.version = '1.0.0'
|
10
|
+
spec.authors = { 'Arkana' => 'https://github.com/rogerluan/arkana' }
|
11
|
+
spec.source = { path: "./" }
|
12
|
+
spec.source_files = 'Sources/**/*.swift'
|
13
|
+
spec.ios.deployment_target = '11.0'
|
14
|
+
spec.swift_version = '5.0'
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
// swift-tools-version: 5.6
|
2
|
+
|
3
|
+
// This file was autogenerated by Arkana. Do not attempt to modify it.
|
4
|
+
|
5
|
+
import PackageDescription
|
6
|
+
|
7
|
+
let package = Package(
|
8
|
+
name: "<%= @import_name %>Interfaces",
|
9
|
+
platforms: [
|
10
|
+
.macOS(.v11),
|
11
|
+
.iOS(.v11),
|
12
|
+
],
|
13
|
+
products: [
|
14
|
+
.library(
|
15
|
+
name: "<%= @import_name %>Interfaces",
|
16
|
+
targets: ["<%= @import_name %>Interfaces"]
|
17
|
+
),
|
18
|
+
],
|
19
|
+
dependencies: [],
|
20
|
+
targets: [
|
21
|
+
.target(
|
22
|
+
name: "<%= @import_name %>Interfaces",
|
23
|
+
dependencies: [],
|
24
|
+
path: "Sources"
|
25
|
+
),
|
26
|
+
]
|
27
|
+
)
|
@@ -0,0 +1,3 @@
|
|
1
|
+
# <%= @namespace %>Interfaces
|
2
|
+
|
3
|
+
This Package was autogenerated by [Arkana](https://github.com/rogerluan/arkana). Do not attempt to modify it manually, otherwise your changes will be overriden in the next code generation process. Please visit [Arkana](https://github.com/rogerluan/arkana) to read more.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
// swift-tools-version: 5.6
|
2
|
+
|
3
|
+
// This file was autogenerated by Arkana. Do not attempt to modify it.
|
4
|
+
|
5
|
+
import PackageDescription
|
6
|
+
|
7
|
+
let package = Package(
|
8
|
+
name: "<%= @import_name %>",
|
9
|
+
platforms: [
|
10
|
+
.macOS(.v11),
|
11
|
+
.iOS(.v11),
|
12
|
+
],
|
13
|
+
products: [
|
14
|
+
.library(
|
15
|
+
name: "<%= @import_name %>",
|
16
|
+
targets: ["<%= @import_name %>"]
|
17
|
+
),
|
18
|
+
],
|
19
|
+
dependencies: [
|
20
|
+
.package(name: "<%= @import_name%>Interfaces", path: "../<%= @import_name%>Interfaces"),
|
21
|
+
],
|
22
|
+
targets: [
|
23
|
+
.target(
|
24
|
+
name: "<%= @import_name %>",
|
25
|
+
dependencies: ["<%= @import_name %>Interfaces"],
|
26
|
+
path: "Sources"
|
27
|
+
),
|
28
|
+
<% if @should_generate_unit_tests %>
|
29
|
+
.testTarget(
|
30
|
+
name: "<%= @import_name %>Tests",
|
31
|
+
dependencies: ["<%= @import_name %>"],
|
32
|
+
path: "Tests"
|
33
|
+
),
|
34
|
+
<% end %>
|
35
|
+
]
|
36
|
+
)
|
@@ -0,0 +1,4 @@
|
|
1
|
+
# <%= @namespace %>
|
2
|
+
|
3
|
+
|
4
|
+
This Package is autogenerated by [Arkana](https://github.com/rogerluan/arkana). Do not attempt to modify it manually, otherwise your changes will be overriden on the next code generation process. Please visit [Arkana](https://github.com/rogerluan/arkana) to read more.
|
data/lib/arkana.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "arkana/config_parser"
|
4
|
+
require_relative "arkana/encoder"
|
5
|
+
require_relative "arkana/helpers/dotenv_helper"
|
6
|
+
require_relative "arkana/models/template_arguments"
|
7
|
+
require_relative "arkana/salt_generator"
|
8
|
+
require_relative "arkana/swift_code_generator"
|
9
|
+
require_relative "arkana/version"
|
10
|
+
|
11
|
+
# Top-level namespace for Arkana's execution entry point. When ran from CLI, `Arkana.run` is what is invoked.
|
12
|
+
module Arkana
|
13
|
+
def self.run(arguments)
|
14
|
+
config = ConfigParser.parse(arguments)
|
15
|
+
salt = SaltGenerator.generate
|
16
|
+
DotenvHelper.load(config)
|
17
|
+
|
18
|
+
begin
|
19
|
+
environment_secrets = Encoder.encode!(
|
20
|
+
keys: config.environment_keys,
|
21
|
+
salt: salt,
|
22
|
+
current_flavor: config.current_flavor,
|
23
|
+
environments: config.environments,
|
24
|
+
)
|
25
|
+
global_secrets = Encoder.encode!(
|
26
|
+
keys: config.global_secrets,
|
27
|
+
salt: salt,
|
28
|
+
current_flavor: config.current_flavor,
|
29
|
+
environments: config.environments,
|
30
|
+
)
|
31
|
+
rescue StandardError => e
|
32
|
+
# TODO: Improve this by creating an Env/Debug helper
|
33
|
+
puts("Something went wrong when parsing and encoding your secrets.")
|
34
|
+
puts("Current Flavor: #{config.current_flavor}")
|
35
|
+
puts("Dotenv Filepath: #{config.dotenv_filepath}")
|
36
|
+
puts("Config Filepath: #{arguments.config_filepath}")
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
template_arguments = TemplateArguments.new(
|
40
|
+
environment_secrets: environment_secrets,
|
41
|
+
global_secrets: global_secrets,
|
42
|
+
config: config,
|
43
|
+
salt: salt,
|
44
|
+
)
|
45
|
+
SwiftCodeGenerator.generate(
|
46
|
+
template_arguments: template_arguments,
|
47
|
+
config: config,
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arkana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Oba
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-07-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yaml
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
13
55
|
description:
|
14
56
|
email:
|
15
57
|
- rogerluan.oba@gmail.com
|
@@ -18,14 +60,42 @@ executables:
|
|
18
60
|
extensions: []
|
19
61
|
extra_rdoc_files: []
|
20
62
|
files:
|
21
|
-
- LICENSE
|
22
63
|
- bin/arkana
|
64
|
+
- bin/console
|
65
|
+
- bin/setup
|
66
|
+
- lib/arkana.rb
|
67
|
+
- lib/arkana/config_parser.rb
|
68
|
+
- lib/arkana/encoder.rb
|
69
|
+
- lib/arkana/helpers/dotenv_helper.rb
|
70
|
+
- lib/arkana/helpers/string.rb
|
71
|
+
- lib/arkana/helpers/swift_template_helper.rb
|
72
|
+
- lib/arkana/helpers/ui.rb
|
73
|
+
- lib/arkana/models/arguments.rb
|
74
|
+
- lib/arkana/models/config.rb
|
75
|
+
- lib/arkana/models/salt.rb
|
76
|
+
- lib/arkana/models/secret.rb
|
77
|
+
- lib/arkana/models/template_arguments.rb
|
78
|
+
- lib/arkana/models/type.rb
|
79
|
+
- lib/arkana/salt_generator.rb
|
80
|
+
- lib/arkana/swift_code_generator.rb
|
81
|
+
- lib/arkana/templates/arkana.podspec.erb
|
82
|
+
- lib/arkana/templates/arkana.swift.erb
|
83
|
+
- lib/arkana/templates/arkana_protocol.swift.erb
|
84
|
+
- lib/arkana/templates/arkana_tests.swift.erb
|
85
|
+
- lib/arkana/templates/interfaces.podspec.erb
|
86
|
+
- lib/arkana/templates/interfaces_package.swift.erb
|
87
|
+
- lib/arkana/templates/interfaces_readme.erb
|
88
|
+
- lib/arkana/templates/package.swift.erb
|
89
|
+
- lib/arkana/templates/readme.erb
|
90
|
+
- lib/arkana/version.rb
|
23
91
|
homepage: https://github.com/rogerluan/arkana
|
24
|
-
licenses:
|
92
|
+
licenses:
|
93
|
+
- BSD-2-Clause
|
25
94
|
metadata:
|
26
95
|
homepage_uri: https://github.com/rogerluan/arkana
|
27
96
|
source_code_uri: https://github.com/rogerluan/arkana
|
28
97
|
changelog_uri: https://github.com/rogerluan/arkana/blob/main/CHANGELOG.md
|
98
|
+
rubygems_mfa_required: 'true'
|
29
99
|
post_install_message:
|
30
100
|
rdoc_options: []
|
31
101
|
require_paths:
|
@@ -34,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
104
|
requirements:
|
35
105
|
- - ">="
|
36
106
|
- !ruby/object:Gem::Version
|
37
|
-
version: 2.
|
107
|
+
version: 2.7.0
|
38
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
109
|
requirements:
|
40
110
|
- - ">="
|
@@ -44,5 +114,6 @@ requirements: []
|
|
44
114
|
rubygems_version: 3.1.6
|
45
115
|
signing_key:
|
46
116
|
specification_version: 4
|
47
|
-
summary: Store your
|
117
|
+
summary: Store your keys and secrets away from your source code. Designed for Android
|
118
|
+
and iOS projects.
|
48
119
|
test_files: []
|
data/LICENSE
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
BSD 2-Clause License
|
2
|
-
|
3
|
-
Copyright (c) 2022, Roger Oba
|
4
|
-
All rights reserved.
|
5
|
-
|
6
|
-
Redistribution and use in source and binary forms, with or without
|
7
|
-
modification, are permitted provided that the following conditions are met:
|
8
|
-
|
9
|
-
1. Redistributions of source code must retain the above copyright notice, this
|
10
|
-
list of conditions and the following disclaimer.
|
11
|
-
|
12
|
-
2. Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
this list of conditions and the following disclaimer in the documentation
|
14
|
-
and/or other materials provided with the distribution.
|
15
|
-
|
16
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
20
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|