Add "fourier bundle" command (#2953)

This commit is contained in:
Pedro Piñera Buendía 2021-05-22 12:49:33 +02:00 committed by GitHub
parent 6e58ef29d6
commit 8c83c86529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 599 additions and 208 deletions

45
.github/workflows/release-dry-run.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Release dry-run
on:
push:
branches:
- main
pull_request:
paths:
- Gemfile*
- Package.swift
- Package.resolved
- Sources/**
- Project.swift
env:
RUBY_VERSION: '3.0.1'
TUIST_STATS_OPT_OUT: true
jobs:
bundle-all:
name: Bundle tuist and tuistenv with Xcode ${{ matrix.xcode }}
runs-on: macos-latest
strategy:
matrix:
xcode: ['12']
steps:
- uses: actions/checkout@v1
- name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
- uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Bundle install
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Build
run: |
./fourier bundle all

View File

@ -3,7 +3,6 @@ inherit_gem:
rubocop-rails_config:
- config/rails.yml
require:
- rubocop-rake
- rubocop-minitest
Style/ClassAndModuleChildren:
@ -20,7 +19,6 @@ AllCops:
DisplayStyleGuide: true
Include:
- Gemfile
- Rakefile
- projects/fourier/**/*.rb
Exclude:

View File

@ -7,7 +7,6 @@ ruby "3.0.1"
gem "cucumber", "~> 6.0"
gem "rake", "~> 13.0"
gem "simctl", "~> 1.6"
gem "encrypted-environment", "~> 0.2.0"
gem "google-cloud-storage", "~> 1.31"
gem "colorize", "~> 0.8.1"
gem "cocoapods", "~> 1.10"
@ -22,6 +21,7 @@ gem "zeitwerk", "~> 2.4"
gem "cli-kit", "~> 3.3"
gem "semantic", "~> 1.6"
gem "down", "~> 5.2"
gem "ejson", "~> 1.2"
group :test do
gem "mocha", "~> 1.12"

View File

@ -75,20 +75,20 @@ GEM
colorize (0.8.1)
concurrent-ruby (1.1.8)
crass (1.0.6)
cucumber (6.0.0)
cucumber (6.1.0)
builder (~> 3.2, >= 3.2.4)
cucumber-core (~> 9.0, >= 9.0.0)
cucumber-core (~> 9.0, >= 9.0.1)
cucumber-create-meta (~> 4.0, >= 4.0.0)
cucumber-cucumber-expressions (~> 12.1, >= 12.1.1)
cucumber-gherkin (~> 18.1, >= 18.1.0)
cucumber-html-formatter (~> 13.0, >= 13.0.0)
cucumber-messages (~> 15.0, >= 15.0.0)
cucumber-wire (~> 5.0, >= 5.0.0)
cucumber-wire (~> 5.0, >= 5.0.1)
diff-lcs (~> 1.4, >= 1.4.4)
mime-types (~> 3.3, >= 3.3.1)
multi_test (~> 0.1, >= 0.1.2)
sys-uname (~> 1.2, >= 1.2.2)
cucumber-core (9.0.0)
cucumber-core (9.0.1)
cucumber-gherkin (~> 18.1, >= 18.1.0)
cucumber-messages (~> 15.0, >= 15.0.0)
cucumber-tag-expressions (~> 3.0, >= 3.0.1)
@ -103,24 +103,21 @@ GEM
cucumber-messages (15.0.0)
protobuf-cucumber (~> 3.10, >= 3.10.8)
cucumber-tag-expressions (3.0.1)
cucumber-wire (5.0.0)
cucumber-core (~> 9.0, >= 9.0.0)
cucumber-wire (5.0.1)
cucumber-core (~> 9.0, >= 9.0.1)
cucumber-cucumber-expressions (~> 12.1, >= 12.1.1)
cucumber-messages (~> 15.0, >= 15.0.0)
declarative (0.0.20)
declarative-option (0.1.0)
diff-lcs (1.4.4)
digest-crc (0.6.3)
rake (>= 12.0.0, < 14.0.0)
down (5.2.1)
addressable (~> 2.5)
ejson (1.2.1)
encrypted-environment (0.2.0)
ejson (~> 1.2)
erubi (1.10.0)
escape (0.0.4)
ethon (0.12.0)
ffi (>= 1.3.0)
ethon (0.14.0)
ffi (>= 1.15.0)
faraday (1.4.1)
faraday-excon (~> 1.1)
faraday-net_http (~> 1.0)
@ -144,7 +141,7 @@ GEM
rexml
signet (~> 0.14)
webrick
google-apis-iamcredentials_v1 (0.2.0)
google-apis-iamcredentials_v1 (0.3.0)
google-apis-core (~> 0.1)
google-apis-storage_v1 (0.3.0)
google-apis-core (~> 0.1)
@ -162,7 +159,7 @@ GEM
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.16.0)
googleauth (0.16.2)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@ -174,7 +171,7 @@ GEM
i18n (1.8.10)
concurrent-ruby (~> 1.0)
json (2.5.1)
jwt (2.2.2)
jwt (2.2.3)
loofah (2.9.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@ -184,8 +181,7 @@ GEM
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0225)
mini_mime (1.0.2)
mini_portile2 (2.5.1)
mini_mime (1.1.0)
minitest (5.14.4)
minitest-reporters (1.4.3)
ansi
@ -201,8 +197,7 @@ GEM
nap (1.1.0)
naturally (2.2.1)
netrc (0.11.0)
nokogiri (1.11.4)
mini_portile2 (~> 2.5.0)
nokogiri (1.11.4-x86_64-darwin)
racc (~> 1.4)
octokit (4.21.0)
faraday (>= 0.9)
@ -235,9 +230,9 @@ GEM
rainbow (3.0.0)
rake (13.0.3)
regexp_parser (2.1.1)
representable (3.0.4)
representable (3.1.1)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
@ -294,6 +289,7 @@ GEM
ffi (~> 1.1)
thor (1.1.0)
thread_safe (0.3.6)
trailblazer-option (0.1.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (1.2.9)
@ -310,7 +306,7 @@ GEM
zeitwerk (2.4.2)
PLATFORMS
ruby
x86_64-darwin-20
DEPENDENCIES
byebug (~> 11.1)
@ -320,7 +316,7 @@ DEPENDENCIES
colorize (~> 0.8.1)
cucumber (~> 6.0)
down (~> 5.2)
encrypted-environment (~> 0.2.0)
ejson (~> 1.2)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
minitest

171
Rakefile
View File

@ -1,171 +0,0 @@
# frozen_string_literal: true
require "rake/testtask"
require "rubygems"
require "cucumber"
require "cucumber/rake/task"
require "mkmf"
require "fileutils"
require "google/cloud/storage"
require "encrypted/environment"
require "colorize"
require "highline"
require "tmpdir"
require "json"
require "zip"
require "macho"
desc("Install git hooks")
task :install_git_hooks do
system("cp hooks/pre-commit .git/hooks/pre-commit")
system("chmod u+x .git/hooks/pre-commit")
puts("pre-commit hook installed on .git/hooks/")
end
desc("Builds and archive a release version of tuist and tuistenv for local testing.")
task :local_package do
package
end
desc("Builds, archives, and publishes tuist and tuistenv for release")
task :release, [:version] do |_task, options|
decrypt_secrets
release(options[:version])
end
desc("Publishes the installation scripts")
task :release_scripts do
decrypt_secrets
release_scripts
end
desc("Encrypt secret keys")
task :encrypt_secrets do
Encrypted::Environment.encrypt_ejson("secrets.ejson", private_key: ENV["SECRET_KEY"])
end
def decrypt_secrets
Encrypted::Environment.load_from_ejson("secrets.ejson", private_key: ENV["SECRET_KEY"])
end
def release_scripts
bucket = storage.bucket("tuist-releases")
print_section("Uploading installation scripts to the tuist-releases bucket on GCS")
bucket.create_file("script/install", "scripts/install").acl.public!
bucket.create_file("script/uninstall", "scripts/uninstall").acl.public!
end
def package
print_section("Building tuist")
FileUtils.mkdir_p("build")
system("swift", "build", "--product", "tuist", "--configuration", "release")
system(
"swift", "build",
"--product", "ProjectDescription",
"--configuration", "release",
"-Xswiftc", "-enable-library-evolution",
"-Xswiftc", "-emit-module-interface",
"-Xswiftc", "-emit-module-interface-path",
"-Xswiftc", ".build/release/ProjectDescription.swiftinterface"
)
system(
"swift", "build",
"--product", "ProjectAutomation",
"--configuration", "release",
"-Xswiftc", "-enable-library-evolution",
"-Xswiftc", "-emit-module-interface",
"-Xswiftc", "-emit-module-interface-path",
"-Xswiftc", ".build/release/ProjectAutomation.swiftinterface"
)
system("swift", "build", "--product", "tuistenv", "--configuration", "release")
build_templates_path = File.join(__dir__, ".build/release/Templates")
script_path = File.join(__dir__, ".build/release/script")
vendor_path = File.join(__dir__, ".build/release/vendor")
FileUtils.rm_rf(build_templates_path) if File.exist?(build_templates_path)
FileUtils.cp_r(File.expand_path("Templates", __dir__), build_templates_path)
FileUtils.rm_rf(script_path) if File.exist?(script_path)
FileUtils.cp_r(File.expand_path("script", __dir__), script_path)
FileUtils.cp_r(File.expand_path("projects/tuist/vendor", __dir__), vendor_path)
File.delete("tuist.zip") if File.exist?("tuist.zip")
File.delete("tuistenv.zip") if File.exist?("tuistenv.zip")
Dir.chdir(".build/release") do
system(
"zip", "-q", "-r", "--symlinks",
"tuist.zip", "tuist",
"ProjectDescription.swiftmodule",
"ProjectDescription.swiftdoc",
"libProjectDescription.dylib",
"ProjectDescription.swiftinterface",
"ProjectAutomation.swiftmodule",
"ProjectAutomation.swiftdoc",
"libProjectAutomation.dylib",
"ProjectAutomation.swiftinterface",
"Templates",
"vendor",
"script"
)
system("zip", "-q", "-r", "--symlinks", "tuistenv.zip", "tuistenv")
end
FileUtils.cp(".build/release/tuist.zip", "build/tuist.zip")
FileUtils.cp(".build/release/tuistenv.zip", "build/tuistenv.zip")
end
def release(version)
if version.nil?
version = cli.ask("Introduce the released version:")
end
puts "Releasing #{version} 🚀"
package
bucket = storage.bucket("tuist-releases")
print_section("Uploading to the tuist-releases bucket on GCS")
bucket.create_file("build/tuist.zip", "#{version}/tuist.zip").acl.public!
bucket.create_file("build/tuistenv.zip", "#{version}/tuistenv.zip").acl.public!
bucket.create_file("build/tuist.zip", "latest/tuist.zip").acl.public!
bucket.create_file("build/tuistenv.zip", "latest/tuistenv.zip").acl.public!
Dir.mktmpdir do |tmp_dir|
version_path = File.join(tmp_dir, "version")
File.write(version_path, version)
bucket.create_file(version_path, "latest/version").acl.public!
end
end
def system(*args)
Kernel.system(*args) || abort
end
def cli
@cli ||= HighLine.new
end
def storage
@storage ||= Google::Cloud::Storage.new(
project_id: ENV["GCS_PROJECT_ID"],
credentials: {
type: ENV["GCS_TYPE"],
project_id: ENV["GCS_PROJECT_ID"],
private_key_id: ENV["GCS_PRIVATE_KEY_ID"],
private_key: ENV["GCS_PRIVATE_KEY"],
client_email: ENV["GCS_CLIENT_EMAIL"],
client_id: ENV["GCS_CLIENT_ID"],
auth_uri: ENV["GCS_AUTH_URI"],
token_uri: ENV["GCS_TOKEN_URI"],
auth_provider_x509_cert_url: ENV["GCS_AUTH_PROVIDER_X509_CERT_URL"],
client_x509_cert_url: ENV["GCS_CLIENT_X509_CERT_URL"],
}
)
end
def print_section(text)
puts(text.bold.green)
end

View File

@ -10,10 +10,9 @@ let config = TapestryConfig(
.pre(tool: "sudo", arguments: ["xcode-select", "-s", "/Applications/Xcode_12.app"]),
.pre(.dependenciesCompatibility([.spm(.all)])),
.pre(tool: "swift", arguments: ["test"]),
// .pre(tool: "bundle", arguments: ["exec", "rake", "features"]),
.pre(.docsUpdate),
.post(tool: "bundle", arguments: ["exec", "rake", "release[\(Argument.version)]"]),
.post(tool: "bundle", arguments: ["exec", "rake", "release_scripts"]),
.post(tool: "./fourier", arguments: ["release", "tuist", "\(Argument.version)"]),
.post(tool: "./fourier", arguments: ["release", "scripts"]),
.post(
.githubRelease(
owner: "tuist",

View File

@ -43,7 +43,7 @@ To start working on the project, we can follow the steps below:
- Ensure you have the NodeJS version specified in the `.nvmrc`
- Ensure you have the Ruby version specified in the `.ruby-version`
- Run `bundle install` to automatically install the required dependencies
- Run `rake install_git_hooks` to automatically format the code following Tuist's conventions
- Run `./fourier up` to automatically format the code following Tuist's conventions
- Open `Package.swift` using Xcode
:::note Xcode

View File

@ -41,6 +41,12 @@ module Fourier
desc "update", "Update project's components"
subcommand "update", Commands::Update
desc "bundle", "Bundle tuist and tuistenv"
subcommand "bundle", Commands::Bundle
desc "encrypt", "Encrypt content in the repository"
subcommand "encrypt", Commands::Encrypt
desc "focus TARGET", "Edit Tuist's project focusing on the target TARGET"
def focus(target)
Services::Focus.call(target: target)
@ -106,6 +112,9 @@ module Fourier
Services::Check.call
end
desc "release", "Release the Tuist"
subcommand "release", Commands::Release
def self.exit_on_failure?
true
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require "tmpdir"
module Fourier
module Commands
class Bundle < Base
desc "tuist", "Bundle tuist"
option(
:output,
desc: "The directory in which the vendored tuist will be generated",
type: :string,
required: false,
aliases: :p,
)
def tuist
output_directory = options[:output]
output_directory ||= File.expand_path("build", Constants::ROOT_DIRECTORY)
Services::Bundle::Tuist.call(output_directory: output_directory)
end
desc "tuistenv", "Bundle tuistenv"
option(
:output,
desc: "The directory in which the vendored tuistenv will be generated",
type: :string,
required: false,
aliases: :p,
)
def tuistenv
output_directory = options[:output]
output_directory ||= File.expand_path("build", Constants::ROOT_DIRECTORY)
Services::Bundle::Tuistenv.call(output_directory: output_directory)
end
desc "all", "Bundle tuistenv and tuist"
option(
:output,
desc: "The directory in which the vendored tuist and tuistenv will be generated",
type: :string,
required: false,
aliases: :p,
)
def all
output_directory = options[:output]
output_directory ||= File.expand_path("build", Constants::ROOT_DIRECTORY)
Dir.mktmpdir do |tmp_dir|
Services::Bundle::Tuist.call(
output_directory: output_directory,
build_directory: tmp_dir
)
Services::Bundle::Tuistenv.call(
output_directory: output_directory,
build_directory: tmp_dir
)
end
end
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Fourier
module Commands
class Encrypt < Base
desc "secrets", "Encrypt the secrets in this repository"
def secrets
Services::Encrypt::Secrets.call
end
end
end
end

View File

@ -4,6 +4,7 @@ module Fourier
class GitHub < Base
desc "cancel-workflows SUBCOMMAND ...ARGS", "Cancels all the running workflows"
def cancel_workflows
Utilities::Secrets.decrypt
Services::GitHub::CancelWorkflows.call
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Fourier
module Commands
class Release < Base
desc "tuist VERSION", "Bundles and uploads Tuist to GCS"
def tuist(version)
Utilities::Secrets.decrypt
output_directory ||= File.expand_path("build", Constants::ROOT_DIRECTORY)
Services::Bundle::Tuist.call(output_directory: output_directory)
Services::Bundle::Tuistenv.call(output_directory: output_directory)
Utilities::Output.section("Uploading tuist and tuistenv scripts to GCS...")
Services::Release::Tuist.call(
version: version,
tuistenv_zip_path: File.join(output_directory, "tuist.zip"),
tuist_zip_path: File.join(output_directory, "tuistenv.zip"),
)
Utilities::Output.success("tuist and tuistenv uploaded to GCS")
end
desc "scripts", "Bundles and uploads the installation scripts to GCS"
def scripts
Utilities::Secrets.decrypt
Utilities::Output.section("Uploading installation scripts to GCS...")
Services::Release::Scripts.call
Utilities::Output.success("Scripts successfully uploaded")
end
end
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
module Fourier
module Constants
module GoogleCloud
RELEASES_BUCKET = "tuist-releases"
end
end
end

View File

@ -0,0 +1,124 @@
# frozen_string_literal: true
require "fileutils"
require "tmpdir"
module Fourier
module Services
module Bundle
class Tuist < Base
attr_reader :output_directory
attr_reader :build_directory
def initialize(output_directory:, build_directory: nil)
@output_directory = output_directory
@build_directory = build_directory
end
def call
output_directory = File.expand_path("build", Constants::ROOT_DIRECTORY) if output_directory.nil?
FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory)
in_build_directory do |build_directory|
Utilities::Output.section("Building Tuist...")
build_tuist(build_directory: build_directory)
Utilities::Output.section("Building ProjectAutomation...")
build_project_automation(build_directory: build_directory)
Utilities::Output.section("Building ProjectDescription...")
build_project_description(build_directory: build_directory)
Dir.mktmpdir do |vendor_directory|
FileUtils.cp_r(
File.expand_path("projects/tuist/vendor", Constants::ROOT_DIRECTORY),
File.expand_path("vendor", vendor_directory)
)
FileUtils.cp_r(
File.expand_path("Templates", Constants::ROOT_DIRECTORY),
File.expand_path("Templates", vendor_directory)
)
[
"tuist", "ProjectDescription.swiftmodule", "ProjectDescription.swiftdoc",
"libProjectDescription.dylib", "ProjectDescription.swiftinterface",
"ProjectAutomation.swiftmodule", "ProjectAutomation.swiftdoc", "ProjectAutomation.swiftinterface"
].each do |file|
FileUtils.cp(
File.expand_path("release/#{file}", build_directory),
File.expand_path(file, vendor_directory)
)
end
Dir.chdir(vendor_directory) do
output_zip_path = File.expand_path("tuist.zip", output_directory)
Utilities::Output.section("Generating #{output_zip_path}...")
Utilities::System.system(
"zip", "-q", "-r", "--symlinks",
output_zip_path,
"tuist",
"ProjectDescription.swiftmodule",
"ProjectDescription.swiftdoc",
"libProjectDescription.dylib",
"ProjectDescription.swiftinterface",
"ProjectAutomation.swiftmodule",
"ProjectAutomation.swiftdoc",
"ProjectAutomation.swiftinterface",
"Templates",
"vendor"
)
end
end
end
end
private
def in_build_directory
unless build_directory.nil?
yield(build_directory)
else
Dir.mktmpdir do |tmp_dir|
yield(tmp_dir)
end
end
end
def build_tuist(build_directory:)
Utilities::System.system(
"swift", "build",
"--product", "tuist",
"--configuration", "release",
"--build-path", build_directory,
"--package-path", Constants::ROOT_DIRECTORY
)
end
def build_project_description(build_directory:)
Utilities::System.system(
"swift", "build",
"--product", "ProjectDescription",
"--configuration", "release",
"-Xswiftc", "-enable-library-evolution",
"-Xswiftc", "-emit-module-interface",
"-Xswiftc", "-emit-module-interface-path",
"-Xswiftc", File.expand_path("release/ProjectDescription.swiftinterface", build_directory),
"--build-path", build_directory,
"--package-path", Constants::ROOT_DIRECTORY
)
end
def build_project_automation(build_directory:)
Utilities::System.system(
"swift", "build",
"--product", "ProjectAutomation",
"--configuration", "release",
"-Xswiftc", "-enable-library-evolution",
"-Xswiftc", "-emit-module-interface",
"-Xswiftc", "-emit-module-interface-path",
"-Xswiftc", File.expand_path("release/ProjectAutomation.swiftinterface", build_directory),
"--build-path", build_directory,
"--package-path", Constants::ROOT_DIRECTORY
)
end
end
end
end
end

View File

@ -0,0 +1,67 @@
# frozen_string_literal: true
require "fileutils"
require "tmpdir"
module Fourier
module Services
module Bundle
class Tuistenv < Base
attr_reader :output_directory
attr_reader :build_directory
def initialize(output_directory:, build_directory: nil)
@output_directory = output_directory
@build_directory = build_directory
end
def call
output_directory = File.expand_path("build", Constants::ROOT_DIRECTORY) if output_directory.nil?
FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory)
in_build_directory do |build_directory|
Utilities::Output.section("Building Tuistenv...")
build_tuistenv(build_directory: build_directory)
Dir.mktmpdir do |vendor_directory|
FileUtils.cp(
File.expand_path("release/tuistenv", build_directory),
File.expand_path("tuistenv", vendor_directory)
)
Dir.chdir(vendor_directory) do
output_zip_path = File.expand_path("tuistenv.zip", output_directory)
Utilities::Output.section("Generating #{output_zip_path}...")
Utilities::System.system(
"zip", "-q", "-r", "--symlinks",
output_zip_path,
"tuistenv"
)
end
end
end
end
private
def in_build_directory
unless build_directory.nil?
yield(build_directory)
else
Dir.mktmpdir do |tmp_dir|
yield(tmp_dir)
end
end
end
def build_tuistenv(build_directory:)
Utilities::System.system(
"swift", "build",
"--product", "tuistenv",
"--configuration", "release",
"--build-path", build_directory,
"--package-path", Constants::ROOT_DIRECTORY
)
end
end
end
end
end

View File

@ -5,13 +5,12 @@ module Fourier
class CancelWorkflows < Base
attr_reader :github_client
def initialize(github_client: Utilities::GitHubClient)
def initialize(github_client: Utilities::GitHubClient.new)
@github_client = github_client
end
def call
Utilities::Secrets.decrypt
runs = github_client.repository_workflow_runs(Constants::REPOSITORY, status: "queued")
runs = github_client.repository_workflow_runs(Constants::REPOSITORY, { status: "queued" })
runs[:workflow_runs].each do |run|
puts "Cancelling workflow run with id: #{run[:id]}"
github_client.cancel_workflow_run(Constants::REPOSITORY, run[:id])

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Fourier
module Services
module Release
class Scripts < Base
attr_reader :storage
def initialize(
storage: Fourier::Utilities::GoogleCloudStorage.new
)
@storage = storage
end
def call
bucket = storage.bucket(Constants::GoogleCloud::RELEASES_BUCKET)
install_path = File.expand_path("script/install", Constants::ROOT_DIRECTORY)
uninstall_path = File.expand_path("script/uninstall", Constants::ROOT_DIRECTORY)
bucket.create_file(install_path, "scripts/install").acl.public!
bucket.create_file(uninstall_path, "scripts/uninstall").acl.public!
end
end
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Fourier
module Services
module Release
class Tuist < Base
attr_reader :tuistenv_zip_path, :tuist_zip_path, :storage, :version
def initialize(
version:,
tuistenv_zip_path:,
tuist_zip_path:,
storage: Utilities::GoogleCloudStorage.new()
)
@version = version
@tuistenv_zip_path = tuistenv_zip_path
@tuist_zip_path = tuist_zip_path
@storage = storage
end
def call
bucket = storage.bucket(Constants::GoogleCloud::RELEASES_BUCKET)
bucket.create_file(tuist_zip_path, "#{version}/tuist.zip").acl.public!
bucket.create_file(tuistenv_zip_path, "#{version}/tuistenv.zip").acl.public!
bucket.create_file(tuist_zip_path, "latest/tuist.zip").acl.public!
bucket.create_file(tuistenv_zip_path, "latest/tuistenv.zip").acl.public!
Dir.mktmpdir do |tmp_dir|
version_path = File.join(tmp_dir, "version")
File.write(version_path, version)
bucket.create_file(version_path, "latest/version").acl.public!
end
end
end
end
end
end

View File

@ -4,8 +4,18 @@ module Fourier
module Services
class Up < Base
def call
puts "Not implemented yet"
install_git_hooks
end
private
def install_git_hooks
Utilities::Output.section("Installing Git pre-commit hooks")
src_path = File.join(Constants::ROOT_DIRECTORY, "hooks/pre-commit")
dst_path = File.join(Constants::ROOT_DIRECTORY, ".git/hooks/pre-commit")
FileUtils.cp(src_path, dst_path)
Utilities::System.system("chmod", "u+x", dst_path)
end
end
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
require "json"
require "tmpdir"
require "fileutils"
module Fourier
module Utilities
module EncryptedEnvironment
EnvironmentError = Class.new(StandardError)
MissingEjson = Class.new(EnvironmentError)
def self.load_from_ejson(ejson_path, private_key: nil)
decrypt_environment(
ejson_path: ejson_path,
private_key: private_key
).each do |key, value|
ENV[key] = value if key != "_public_key"
end
end
def self.encrypt_ejson(ejson_path, private_key: nil)
with_secrets(ejson_path: ejson_path, private_key: private_key) do |path|
%x(EJSON_KEYDIR=#{path} #{binary_path} encrypt #{ejson_path})
end
end
class << self
private
def binary_path
File.expand_path("../vendor/ejson", __dir__)
end
def decrypt_environment(ejson_path:, private_key: nil)
with_secrets(ejson_path: ejson_path, private_key: private_key) do |path|
output = %x(EJSON_KEYDIR=#{path} #{binary_path} decrypt #{ejson_path})
JSON.parse(output)
end
end
def with_secrets(ejson_path:, private_key: nil)
raise MissingEjson unless File.exist?(ejson_path)
content = File.read(ejson_path)
ejson = JSON.parse(content)
public_key = ejson["_public_key"]
should_delete = false
if !private_key.nil?
secrets_path = Dir.mktmpdir
should_delete = true
File.write(File.join(secrets_path, public_key), private_key)
yield(secrets_path)
else
yield("/opt/ejson/keys/")
end
ensure
FileUtils.remove_dir(secrets_path, true) if should_delete
end
end
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require "google/cloud/storage"
module Fourier
module Utilities
class GoogleCloudStorage
def self.new(environment: ENV)
Google::Cloud::Storage.new(
project_id: ENV["GCS_PROJECT_ID"],
credentials: {
type: ENV["GCS_TYPE"],
project_id: ENV["GCS_PROJECT_ID"],
private_key_id: ENV["GCS_PRIVATE_KEY_ID"],
private_key: ENV["GCS_PRIVATE_KEY"],
client_email: ENV["GCS_CLIENT_EMAIL"],
client_id: ENV["GCS_CLIENT_ID"],
auth_uri: ENV["GCS_AUTH_URI"],
token_uri: ENV["GCS_TOKEN_URI"],
auth_provider_x509_cert_url: ENV["GCS_AUTH_PROVIDER_X509_CERT_URL"],
client_x509_cert_url: ENV["GCS_CLIENT_X509_CERT_URL"],
}
)
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require "colorize"
module Fourier
module Utilities
class Output
def self.section(message)
STDERR.puts(message.cyan.bold)
end
def self.subsection(message)
STDERR.puts(message.cyan)
end
def self.error(message)
STDERR.puts(message.red.bold)
end
def self.warning(message)
STDOUT.puts(message.yellow.bold)
end
def self.success(message)
STDOUT.puts(message.green.bold)
end
end
end
end

View File

@ -1,15 +1,14 @@
# frozen_string_literal: true
require "encrypted/environment"
module Fourier
module Utilities
module Secrets
def self.decrypt
Encrypted::Environment.load_from_ejson(secrets_ejson_path, private_key: ENV["SECRET_KEY"])
EncryptedEnvironment.load_from_ejson(secrets_ejson_path, private_key: ENV["SECRET_KEY"])
end
def self.encrypt
Encrypted::Environment.load_from_ejson(secrets_ejson_path, private_key: ENV["SECRET_KEY"])
EncryptedEnvironment.load_from_ejson(secrets_ejson_path, private_key: ENV["SECRET_KEY"])
end
def self.secrets_ejson_path

BIN
projects/fourier/lib/fourier/vendor/ejson vendored Executable file

Binary file not shown.

View File

@ -7,7 +7,6 @@ module Fourier
class CancelWorkflowsTest < TestCase
def test_cancels_workflows
# Given
Utilities::Secrets.expects(:decrypt)
github_client = mock("github_client")
.responds_like_instance_of(Utilities::GitHubClient)
queued_jobs = {

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require "test_helper"
module Fourier
module Services
class UpTest < TestCase
include TestHelpers::SupressOutput
def test_call_installs_git_hooks
# Given
src_path = File.join(Constants::ROOT_DIRECTORY, "hooks/pre-commit")
dst_path = File.join(Constants::ROOT_DIRECTORY, ".git/hooks/pre-commit")
FileUtils
.expects(:cp)
.with(src_path, dst_path)
Utilities::System
.expects(:system)
.with("chmod", "u+x", dst_path)
# When/Then
supressing_output do
Up.call
end
end
end
end
end