Page 144 of 173

Przygotowanie kodu aplikacji w Ruby on Rails do deployu – część II

Witam w części drugiej poradnika poświęconego przygotowaniu kodu do umieszczenia na serwerze. W części tej zajmiemy się skonstruowaniem modelu który będzie generował nam losowe kawałki kodu oraz ew. losowe ciągi na potrzeby zaśmiecania.

Pierwszą rzeczą jaką sobie ustalimy, będzie to ile potrzebujemy takich komentarzy. Ja założyłem sobie że różnych potrzebujemy około 10^4, tak więc ustawiłem stałą na takową wartość:

    # Ilość komentarzy
    COMMENT_NR = 10**4

Opis klasy którą stworzymy, zacznę niejako od końca. Jednak jest to dość ważne. Nie każdy plik jaki mamy w systemie ma w sobie włączoną obsługę utf-8. Z tego względu przenosząc nasze "losowe" kawałki kodu, musimy m.in. pozbyć się polskich znaków. Wykonać to można bardzo prosto, korzystając z poniższego kodu:

    private

    # Usuwa polskie znaki z komentarzy zeby sie nie czepiał ruby ze nie utf
    def self.to_comment(temp)
      temp.gsub!(/[âäàãáäå�?ăąǎǟǡǻ�?ȃȧẵặ]/,'a')
      temp.gsub!(/[ëêéèẽēĕėẻȅȇẹȩęḙḛ�?ếễểḕḗệ�?]/,'e')
      temp.gsub!(/[�?iìíîĩīĭïỉ�?ịįȉȋḭɨḯ]/,'i')
      temp.gsub!(/[òóôõ�?�?ȯö�?őǒ�?�?ơǫ�?ɵøồốỗổȱȫȭ�?�?ṑṓ�?ớỡởợǭộǿ]/,'o')
      temp.gsub!(/[ùúûũūŭüủůűǔȕȗưụṳųṷṵṹṻǖǜǘǖǚừứữửự]/,'u')
      temp.gsub!(/[ỳýŷỹȳ�?ÿỷẙƴỵ]/,'y')
      temp.gsub!(/[ñǹń]/,'n')
      temp.gsub!(/[çć]/,'c')
      temp.gsub!(/[ß]/,'ss')
      temp.gsub!(/[œ]/,'oe')
      temp.gsub!(/[ij]/,'ij')
      temp.gsub!(/[�?ł]/,'l')
      temp.gsub!(/[ś]/,'s')
      temp.gsub!(/[źż]/,'z')
      temp.gsub!(/\#\{/, '\\#\\{')
      temp
    end

Kolejną (już nie prywatną) metodą, jest metoda generująca losową ilość spacji. Ot tak żeby kod wyglądał na jeszcze bardziej "rozwalony":

    # Zwraca losową ilość spacji
    def self.spaces
      value = ''
      (100...rand(256)).collect { |n| value  << '  ' }.join()
      value
    end

Przyda się też generator losowych stringów:

    # Zwraca losowego stringa
    def self.string
      value = ''
      (50...(rand(256)+51)).collect{ |n|
        chr =  (65 + rand(55)).chr
        value  << chr if chr != '\\'
      }.join
      value
    end

Oraz najważniejsze, czyli generator "kawałków" kodu. Skąd wziąć takie kawałki? Z innych plików z naszego systemu. W ten sposób powstanie "zupa" która na pierwszy rzut oka wygląda strasznie (a taki właśnie efekt chcemy osiągnąć).

Przygotowanie takich śmieciowych kawałków jest bardzo proste. Czytamy katalog app i wybieramy z niego losowe linijki które są kodem (odrzucamy komentarze).

      # Narazie nie ma komentarzy żadnych
      @trash = []

      # Najpierw do zmiennej tymczasowej załadujmy kawałki kodu aplikacji
      app_parts = []
      Find.find(path){ |f|
        # Jeśli to nie plik .rb (czyli z kodem) to idz dalej

        next unless f[f.size-3, f.size-1] == '.rb'
        file = File.open(f, "r")
        file.each_line {|l|
          a = l.strip
          next if a[0] == "\#" || a.size < 3
          app_parts << self.class.to_comment(l.strip)
        }

        break if app_parts.size >= COMMENT_NR
      }

Mając już losowe kawałki kodu, "przemieszajmy" je w proporcjach 3:1 z jakimiś losowo wygenerowanymi stringami:

      COMMENT_NR.times { |nr|
        if nr % 4 == 0
          @trash << self.class.string
        else
          @trash << app_parts[rand(app_parts.size-1)]
        end
      }

Ostatnią rzeczą jakiej potrzebujemy, jest metoda do zwracania losowej linijki:

    # Zwraca linie z jakimś losowym czymś
    def line(line = nil)
      @trash[line] if line
      @trash[rand(@trash.size-1)] unless line
    end

Z całości korzysta się bardzo prosto:

# Tworzymy randomera do generowania smieciowych kawałków tekstu
rand = Randomer.new
# Generujemy losową ilość spacji
rand.class.spaces
# Generujemy sobie jakąś losową linijkę
rand.line

Cały model wygląda tak:

  # Najpierw stworzymy sobie "śmietnisko" komentarzowe
  # Czyli załadujemy dużo różnych kawałków kodu ;)
  class Randomer
    # Ilość komentarzy
    COMMENT_NR = 10**4

    # Załadujemy sobie tutaj do tablicy - losowe komentarze, na zmianę
    # generowane dynamicznie (ciągi znaków) jak i kawałki kodu z innych plików
    def initialize(path = './app/')
      # Narazie nie ma komentarzy żadnych
      @trash = []

      # Najpierw do zmiennej tymczasowej załadujmy kawałki kodu aplikacji
      app_parts = []
      Find.find(path){ |f|
        # Jeśli to nie plik .rb (czyli z kodem) to idz dalej

        next unless f[f.size-3, f.size-1] == '.rb'
        file = File.open(f, "r")
        file.each_line {|l|
          a = l.strip
          next if a[0] == "\#" || a.size < 3
          app_parts << self.class.to_comment(l.strip)
        }

        break if app_parts.size >= COMMENT_NR
      }

      COMMENT_NR.times { |nr|
        if nr % 4 == 0
          @trash << self.class.string
        else
          @trash << app_parts[rand(app_parts.size-1)]
        end
      }
    end

    # Zwraca linie z jakimś losowym czymś
    def line(line = nil)
      @trash[line] if line
      @trash[rand(@trash.size-1)] unless line
    end

    # Zwraca losowego stringa
    def self.string
      value = ''
      (50...(rand(256)+51)).collect{ |n|
        chr =  (65 + rand(55)).chr
        value  << chr if chr != '\\'
      }.join
      value
    end

    # Zwraca losową ilość spacji
    def self.spaces
      value = ''
      (100...rand(256)).collect { |n| value  << '  ' }.join()
      value
    end

    private

    # Usuwa polskie znaki z komentarzy zeby sie nie czepiał ruby ze nie utf
    def self.to_comment(temp)
      temp.gsub!(/[âäàãáäå�?ăąǎǟǡǻ�?ȃȧẵặ]/,'a')
      temp.gsub!(/[ëêéèẽēĕėẻȅȇẹȩęḙḛ�?ếễểḕḗệ�?]/,'e')
      temp.gsub!(/[�?iìíîĩīĭïỉ�?ịįȉȋḭɨḯ]/,'i')
      temp.gsub!(/[òóôõ�?�?ȯö�?őǒ�?�?ơǫ�?ɵøồốỗổȱȫȭ�?�?ṑṓ�?ớỡởợǭộǿ]/,'o')
      temp.gsub!(/[ùúûũūŭüủůűǔȕȗưụṳųṷṵṹṻǖǜǘǖǚừứữửự]/,'u')
      temp.gsub!(/[ỳýŷỹȳ�?ÿỷẙƴỵ]/,'y')
      temp.gsub!(/[ñǹń]/,'n')
      temp.gsub!(/[çć]/,'c')
      temp.gsub!(/[ß]/,'ss')
      temp.gsub!(/[œ]/,'oe')
      temp.gsub!(/[ij]/,'ij')
      temp.gsub!(/[�?ł]/,'l')
      temp.gsub!(/[ś]/,'s')
      temp.gsub!(/[źż]/,'z')
      temp.gsub!(/\#\{/, '\\#\\{')
      temp
    end
  end

W tej części zrobiliśmy "śmieciarza" który będzie nam brudził w kodzie. W następnej (ostatniej już) części sprawimy że to wszystko zacznie działać :)

Tutaj możesz zobaczyć pozostałe części tutoriala:

Przygotowanie kodu aplikacji w Ruby on Rails do deployu – część I

Każdy z Was spotkał się kiedyś na pewno, z problemem przygotowania aplikacji do deployu (umieszczenia na serwerze). Lista rzeczy które trzeba zrobić nie jest duża, ale obejmuje m.in.:

  • Wyczyszczenie cachey, tempów i innych nie potrzebnych plików
  • Usunięcie katalogów repozytoriów (czy to git czy też svn)
  • Usunięcie testów
  • Usunięcie innych plików związanych ze środowiskiem developerskim bądź testowym
  • Ew. "zaciemnienie" kodu

Mówiąc "zaciemnianie" nie mam na myśli stricte obfuscacji. Railsy są środowiskiem gdzie wszystko ma być (i jest) czytelne i przejrzyste. Jednak każdemu z nas (mi na szczęście niezmiernie rzadko) trafiają się klienci którzy chcą mieć aplikacje u siebie.

O ile problem przeniesienia kodu dalej (czyli wyciek) można ograniczyć przy pomocy dobrze napisanej umowy licencyjnej, o tyle "zabaw" z kodem - w wykonaniu klienta już raczej nie.

Dlatego wpadłem na pomysł że "zaśmiecę" kod aplikacji, dużą ilością komentarzy które albo będą wygenerowanymi losowo ciągami znaków, albo kawałkami kodu z innych fragmentów aplikacji. W ten sposób - trzeba będzie się przedrzeć przez gąszcz komentarzy i chaosu aby coś nabrudzić.

Oczywiście nie zabezpieczy to kodu przed osobami które chcą, jednak większość "grzebaczy" widząc takie rzeczy po prostu zrezygnuje z dalszego kombinowania.

Aby uprościć sobie życie, napisałem krótki (300 linijek) skrypt który przygotowuje "minimalistyczną" wersję kodu, na potrzeby wrzucenia na produkcję. Poniżej pokażę Wam jak stworzyć swoją wersję takiego pliku.

Pierwszą rzeczą jaką zrobimy, jest dołączenie do naszego pliku deploy.rb odpowiednich dowiązań. Potrzebujemy biblioteki find do wylistowania plików oraz biblioteki fileutils aby coś z tymi plikami zrobić. Tak więc:

# coding: utf-8
require 'find'
require 'fileutils'

# Skrypt automatyzujący deployment aplikacji
# Usuwa sensowne komentarze, robi taki bałagan w kodzie ;)
# Usuwa to czego klientowi na produkcji nie potrzeba
# Sprzata, czysci i pozostawia absolutne minimum

Dodaliśmy także "magiczny" komentarz mówiący że być może będziemy korzystać z utf-8. Jest to ważne dla Rubiego 1.9.2 i wzwyż. Dodaliśmy także komentarz co nasz skrypt robi.

Mając to minimum, teraz musimy zdefiniować w postaci stałych, co ma być zaciemnione, co usunięte, itp.
Najprościej, tak jak wspomniałem, jest zrobić to w stałych. Zdefiniujmy sobie listę katalogów, z których pliki mają zostać "zaciemnione":

# Które katalogi ma "zaciemniać"
MASK_DIRS = [
  './deploy/app/models',
  './deploy/app/controllers/',
  './deploy/app/mailers/',
  './deploy/db/',
  './deploy/lib/',
  ]

Które katalogi ma usunąć:

# Które katalogi i pliki mają zostać usunięte (ktore nie są potrzebne klientowi)
# Przy okazji usuwa wszystkie katalogi i pliki takie jak temp, cache, .svn
CLEAR_ELEMENTS = [
  './deploy/config/environments/development.rb',
  './deploy/config/environments/test.rb',
  './deploy/db/migrate',
  './deploy/doc',
  './deploy/log',
  './deploy/nbproject',
  './deploy/test',
  './deploy/README',
  './deploy/deploy.rb'
]

I ostatnie, wyrażenia regularne. Niezależnie gdzie dany katalog się znajduje, jeśli będzie zgodny ze wzorcem, to zostanie usunięty:

# Nazwy katalogów i plików tymczasowych które mają zostać usunięte
CLEAR_REGS = [
  'temp',
  'tmp',
  'cache',
  '.svn',
  '.git'
]

Zanim przejdziemy dalej, musimy wyjaśnić sobie jedną rzecz. Czym jest subkatalog "deploy" który przewija się w ścieżkach? Otóż jak zobaczycie dalej, nasz skrypt będzie odpalany z głównego katalogu naszej aplikacji. Jednak aby niczego nie zepsuć czy nie usunąć przez przypadek, do celów deployu wykonamy sobie kopię całego systemu, która trafi właśnie do subkatalogu "deploy/". To właśnie na tej kopii będziemy wykonywać operacje zaciemniania oraz usuwania. Dzięki temu w razie wybrania złych katalogów do usunięcia (załóżmy katalogu lib), nie stracimy ważnych kodów źródłowych.

Warto tutaj wspomnieć że o ile będziemy sterować maskowaniem i usuwaniem plików, o tyle oryginalne komentarze zostaną usunięte z każdego pliku z rozszerzeniem rb.

Zajmijmy się więc naszym "backuperem". Jego zadaniem będzie zrobienie kopii całego systemu do katalogu "deploy/". Jeśli taki katalog istniał, zostanie on usunięty a na jego miejsce zostanie przekopiowana aktualna wersja systemu.

  # Robi kopię "deploymentową" do katalogu /deploy/ w aplikacji
  class Backuper
    def self.run
      deploy = './deploy/'
      if File.directory?(deploy)
        puts "Removing old deployment..."
        FileUtils.rm_rf(deploy)
      end

      files = Dir.glob('*')
      FileUtils.mkdir 'deploy'
      puts "Creating new deployment..."
      FileUtils.cp_r files, 'deploy'
    end
  end

Tutaj za bardzo nie ma co tłumaczyć. Sprawdzamy czy istniał katalog, jeśli tak to go usuwamy. Następnie pobieramy listę plików i katalogów naszego systemu i kopiujemy go do deploy/.

Proste, skuteczne i szybkie :)

Kolejnym modelem jakiego potrzebujemy, jest "cleaner". Model usuwający wszystkie pliki które są dla nas, nie dla klienta. Działa on dwuetapowo. Pierwszym etapem jest usunięcie plików "regularnych", czyli tych z CLEAR_ELEMENTS. Robi się to bardzo prosto. Jeśli ścieżka jest katalogiem, to usuń katalog. Jeśli to plik, usuń plik:

      # Najpierw usuń "stałe" elementy
      CLEAR_ELEMENTS.each{ |e|
        # Jeśli to katalog to go usun
        if File.directory?(e)
          puts "Removing: #{e}"
          FileUtils.rm_rf(e)
        end
        # Jesli plik to tez usun ;)
        if File.exists?(e)
          puts "Removing: #{e}"
          File.delete(e)
        end
      }

Drugim etapem jest usunięcie regularnych. Tutaj po prostu porównujemy nazwę katalogu/pliku z nazwą zawartą w CLEAR_REGS. Jeśli pasują, to usuwamy. Dodatkowo "odtwarzaniu" podlegają katalogi "tmp". Są one czyszczone, jednak sam katalog pozostaje. Zrobiłem tak dlatego że część moich testów wymaga by ten katalog istniał. Jeśli nie macie testów tego typu, możecie się bez tego obejść. Kod przeszukuje całą strukturę, katalog po katalogu, razem z subkatalogami.

      # Teraz elementy regexpowe, czyli jakies tam katalogi
      # ktore moga byc w roznych miejscach
      CLEAR_REGS.each{ |r|
        Find.find('./deploy/') { |f|
          name = f.split('/').last
          if name ==  r
            if File.directory?(f)
              puts "Recreating: #{f}"
              FileUtils.rm_rf(f)
	      # tmp odtwórz bo testy nie zostaną zakonczone pomyslnie (kwestia np resetu passengera w tmp)
              FileUtils.mkdir f if f.split('/').last == 'tmp'
            end
          end
        }
      }

Powyższy kod "obudujemy" w naszą klasę:

  # Usuwa to, czego klient nie potrzebuje
  class Cleaner
    def self.run
      # Najpierw usuń "stałe" elementy
      CLEAR_ELEMENTS.each{ |e|
        # Jeśli to katalog to go usun
        if File.directory?(e)
          puts "Removing: #{e}"
          FileUtils.rm_rf(e)
        end
        # Jesli plik to tez usun ;)
        if File.exists?(e)
          puts "Removing: #{e}"
          File.delete(e)
        end
      }

      # Teraz elementy regexpowe, czyli jakies tam katalogi
      # ktore moga byc w roznych miejscach
      CLEAR_REGS.each{ |r|
        Find.find('./deploy/') { |f|
          name = f.split('/').last
          if name ==  r
            if File.directory?(f)
              puts "Recreating: #{f}"
              FileUtils.rm_rf(f)
	      # tmp odtwórz bo testy nie zostaną zakonczone pomyslnie (kwestia np resetu passengera w tmp)
              FileUtils.mkdir f if f.split('/').last == 'tmp'
            end
          end
        }
      }
    end
  end

Tyle w części pierwszej. W części drugiej zajmiemy się stworzeniem "śmieciarza" który będzie nam "brudził" nasze pliki. Nasz skrypt już spisuje się całkiem nieźle:

  • Wykonuje kopię naszego projektu
  • Usuwa wszystkie niepotrzebne pliki

Tutaj możesz zobaczyć pozostałe części tutoriala:

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑