mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-21 21:26:22 +00:00
deployear los sitios de distintas formas
This commit is contained in:
parent
5ace66f20a
commit
11cf2cb08b
13 changed files with 282 additions and 1 deletions
1
Gemfile
1
Gemfile
|
@ -54,6 +54,7 @@ gem 'mobility'
|
|||
gem 'pundit'
|
||||
gem 'rails-i18n'
|
||||
gem 'rails_warden'
|
||||
gem 'rubyzip'
|
||||
gem 'rugged'
|
||||
gem 'validates_hostname'
|
||||
gem 'whenever', require: false
|
||||
|
|
|
@ -423,6 +423,7 @@ DEPENDENCIES
|
|||
rails_warden
|
||||
rbnacl (< 5.0)
|
||||
rubocop-rails
|
||||
rubyzip
|
||||
rugged
|
||||
sass-rails (~> 5.0)
|
||||
selenium-webdriver
|
||||
|
|
33
app/models/deploy.rb
Normal file
33
app/models/deploy.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
# Este modelo implementa los distintos tipos de alojamiento que provee
|
||||
# Sutty.
|
||||
#
|
||||
# Los datos se guardan en la tabla `deploys`. Para guardar los
|
||||
# atributos, cada modelo tiene que definir su propio `store
|
||||
# :attributes`.
|
||||
class Deploy < ApplicationRecord
|
||||
belongs_to :site
|
||||
|
||||
def deploy
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def limit
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Corre un comando y devuelve true si terminó correctamente
|
||||
def run(cmd)
|
||||
r = 1
|
||||
|
||||
Dir.chdir(site.path) do
|
||||
Open3.popen2(env, cmd, unsetenv_others: true) do |_, _o, t|
|
||||
r = t.value
|
||||
end
|
||||
end
|
||||
|
||||
r.exited?
|
||||
end
|
||||
end
|
77
app/models/deploy_local.rb
Normal file
77
app/models/deploy_local.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Alojamiento local, solo genera el sitio, con lo que no necesita hacer
|
||||
# nada más
|
||||
class DeployLocal < Deploy
|
||||
store :values, accessors: %i[fqdn destination], coder: YAML
|
||||
|
||||
before_create :fqdn!, :destination!
|
||||
before_destroy :remove_destination!
|
||||
|
||||
# Realizamos la construcción del sitio usando Jekyll y un entorno
|
||||
# limpio para no pasarle secretos
|
||||
#
|
||||
# Pasamos variables de entorno mínimas para no filtrar secretos de
|
||||
# Sutty
|
||||
#
|
||||
# TODO: Recolectar estadísticas y enviarlas a la base de datos
|
||||
def deploy
|
||||
yarn && bundle && jekyll_build
|
||||
end
|
||||
|
||||
# Sólo permitimos un deploy local
|
||||
def limit
|
||||
1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Un entorno que solo tiene lo que necesitamos
|
||||
def env
|
||||
paths = [File.dirname(`which bundle`), '/usr/bin']
|
||||
|
||||
{ 'PATH' => paths.join(':'), 'JEKYLL_ENV' => 'production' }
|
||||
end
|
||||
|
||||
def yarn_lock
|
||||
File.join(site.path, 'yarn.lock')
|
||||
end
|
||||
|
||||
def yarn_lock?
|
||||
File.exist? yarn_lock
|
||||
end
|
||||
|
||||
# Corre yarn dentro del repositorio
|
||||
def yarn
|
||||
return unless yarn_lock?
|
||||
|
||||
run 'yarn'
|
||||
end
|
||||
|
||||
def bundle
|
||||
run 'bundle'
|
||||
end
|
||||
|
||||
def jekyll_build
|
||||
run "bundle exec jekyll build --destination \"#{escaped_destination}\""
|
||||
end
|
||||
|
||||
def fqdn!
|
||||
self.fqdn ||= "#{site.name}.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
||||
end
|
||||
|
||||
def destination!
|
||||
self.destination ||= File.join(Rails.root, '_deploy', fqdn)
|
||||
end
|
||||
|
||||
# no debería haber espacios ni caracteres especiales, pero por si
|
||||
# acaso...
|
||||
def escaped_destination
|
||||
Shellwords.escape destination
|
||||
end
|
||||
|
||||
# Eliminar el destino si se elimina el deploy
|
||||
def remove_destination!
|
||||
FileUtils.rm_rf destination
|
||||
end
|
||||
end
|
47
app/models/deploy_zip.rb
Normal file
47
app/models/deploy_zip.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Genera un ZIP a partir del sitio ya construido
|
||||
#
|
||||
# TODO: Firmar con minisign
|
||||
class DeployZip < Deploy
|
||||
store :values, accessors: %i[fqdn destination file path], coder: YAML
|
||||
|
||||
before_create :fqdn!, :destination!
|
||||
before_create :file!, :path!
|
||||
|
||||
# Una vez que el sitio está generado, tomar todos los archivos y
|
||||
# y generar un zip accesible públicamente.
|
||||
#
|
||||
# TODO: Recolectar estadísticas y enviarlas a la base de datos
|
||||
def deploy
|
||||
Dir.chdir(destination) do
|
||||
Zip::File.open(path, Zip::File::CREATE) do |z|
|
||||
Dir.glob('./**/**').each do |f|
|
||||
File.directory?(f) ? z.mkdir(f) : z.add(f, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File.exist? path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Copiamos de DeployLocal para no cargar todos los métodos de
|
||||
# compilación...
|
||||
def fqdn!
|
||||
self.fqdn ||= "#{site.name}.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
||||
end
|
||||
|
||||
def destination!
|
||||
self.destination ||= File.join(Rails.root, '_deploy', fqdn)
|
||||
end
|
||||
|
||||
def file!
|
||||
self.file ||= "#{fqdn}.zip"
|
||||
end
|
||||
|
||||
def path!
|
||||
self.path = File.join(destination, file)
|
||||
end
|
||||
end
|
|
@ -13,6 +13,7 @@ class Site < ApplicationRecord
|
|||
belongs_to :design
|
||||
belongs_to :licencia
|
||||
|
||||
has_many :deploys
|
||||
has_many :roles
|
||||
has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') },
|
||||
through: :roles
|
||||
|
|
13
db/migrate/20190723220002_create_deploys.rb
Normal file
13
db/migrate/20190723220002_create_deploys.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Crea la tabla de deploys posibles para un sitio
|
||||
class CreateDeploys < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :deploys do |t|
|
||||
t.timestamps
|
||||
t.belongs_to :site, index: true
|
||||
t.string :type, index: true
|
||||
t.text :values
|
||||
end
|
||||
end
|
||||
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -12,7 +12,17 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20_190_719_221_653) do
|
||||
ActiveRecord::Schema.define(version: 20_190_723_220_002) do
|
||||
create_table 'deploys', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.integer 'site_id'
|
||||
t.string 'type'
|
||||
t.text 'values'
|
||||
t.index ['site_id'], name: 'index_deploys_on_site_id'
|
||||
t.index ['type'], name: 'index_deploys_on_type'
|
||||
end
|
||||
|
||||
create_table 'designs', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
|
|
|
@ -106,6 +106,9 @@ falta (algunas plantillas tienen requisitos extraños).
|
|||
Las plantillas se instalan como gemas en los sitios, de forma que
|
||||
podemos cambiarla desde las opciones del sitio luego.
|
||||
|
||||
Para poder cambiar el diseño, necesitamos poder editar la configuración
|
||||
del sitio en _config.yml y colocar el nombre de la gema en `theme:`
|
||||
|
||||
## Internamente
|
||||
|
||||
Al crear un sitio, clonar el esqueleto en el lugar correcto. Al
|
||||
|
@ -162,3 +165,50 @@ actualizaciones muestra el historial para que les usuaries vean cuales
|
|||
son los cambios que se van a aplicar. Este historial viene del
|
||||
repositorio git con lo que tenemos que tomarnos la costumbre de escribir
|
||||
commits completos con explicación.
|
||||
|
||||
## Alojamiento
|
||||
|
||||
Para elegir las opciones de alojamiento, agregamos clases que
|
||||
implementan el nombre del alojamiento y sus opciones específicas:
|
||||
|
||||
* Local (sitio.sutty.nl): Deploy local. Genera el sitio en
|
||||
_deploy/name.sutty.nl y lo pone a disposición del servidor web (es
|
||||
como veniamos trabajando hasta ahora). También admite dominios
|
||||
propios.
|
||||
|
||||
Solo se puede tener uno y no es opcional porque lo necesitamos para
|
||||
poder hacer el resto de deploys
|
||||
|
||||
* Neocities: Deploy a neocities.org, necesita API key. Se conecta con
|
||||
neocities usando la API y envía los archivos.
|
||||
|
||||
Solo se puede tener uno (pendiente hablar con neocities si podemos
|
||||
hacer esto).
|
||||
|
||||
* Zip: genera un zip con el contenido del sitio y provee una url de
|
||||
descarga estilo https://sutty.nl/sites/site.zip
|
||||
|
||||
Solo se puede tener uno
|
||||
|
||||
* SSH: Deploy al servidor SSH que especifiquemos. Necesita que le
|
||||
pasemos user@host, puerto y ruta (quizás la ruta no es necesaria y
|
||||
vaya junto con user@host). Informar a les usuaries la llave pública
|
||||
de Sutty para que la agreguen.
|
||||
|
||||
No hay límite a los servidores SSH que se pueden agregar.
|
||||
|
||||
La clase Deploy::* tiene que implementar estas interfaces:
|
||||
|
||||
* `#limit` devuelve un entero que es la cantidad de deploys de este
|
||||
tipo que se pueden agregar, `nil` significa sin límite
|
||||
* `#deploy` el método que realiza el deploy. En realidad este método
|
||||
hay que correrlo por fuera de Sutty...
|
||||
* `#attributes` los valores serializados que acepta el Deploy
|
||||
|
||||
Son modelos que pueden guardar atributos en la base y a través de los
|
||||
que se pueden tomar los datos a través de la API (cuando tengamos un
|
||||
método de deployment).
|
||||
|
||||
El plan es migrar todo esto a <drone.io> de forma que la compilación se
|
||||
haga por separado de sutty. Este es un plan intermedio hasta que
|
||||
tengamos tiempo de hacerlo realmente.
|
||||
|
|
7
test/factories/deploy_local.rb
Normal file
7
test/factories/deploy_local.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :deploy_local do
|
||||
site
|
||||
end
|
||||
end
|
7
test/factories/deploy_zip.rb
Normal file
7
test/factories/deploy_zip.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :deploy_zip do
|
||||
site
|
||||
end
|
||||
end
|
13
test/models/deploy_local_test.rb
Normal file
13
test/models/deploy_local_test.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DeployZipTest < ActiveSupport::TestCase
|
||||
test 'se puede deployear' do
|
||||
deploy_local = create :deploy_local
|
||||
|
||||
assert deploy_local.deploy
|
||||
assert File.directory?(deploy_local.destination)
|
||||
|
||||
assert deploy_local.destroy
|
||||
assert_not File.directory?(deploy_local.destination)
|
||||
end
|
||||
end
|
21
test/models/deploy_zip_test.rb
Normal file
21
test/models/deploy_zip_test.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DeployLocalTest < ActiveSupport::TestCase
|
||||
test 'se puede deployear' do
|
||||
site = create :site
|
||||
local = create :deploy_local, site: site
|
||||
deploy = create :deploy_zip, site: site
|
||||
|
||||
# Primero tenemos que generar el sitio
|
||||
local.deploy
|
||||
|
||||
escaped_path = Shellwords.escape(deploy.path)
|
||||
|
||||
assert deploy.deploy
|
||||
assert File.file?(deploy.path)
|
||||
assert_equal 'application/zip',
|
||||
`file --mime-type "#{escaped_path}"`.split(' ').last
|
||||
|
||||
local.destroy
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue