ABONAMENTE VIDEO REDACȚIA
RO
EN
Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 20
Abonament PDF

Vagrant pentru începători

Carmen Frăţilă
Software engineer
@3Pillar Global



PROGRAMARE


De câte ori ai auzit "Dar funcționează pe mașina mea" sau "Dar la mine pe local merge"? Cât timp îți ia să-ți setezi mediul de lucru? De câte ori ai întâlnit diferențe între serverul de pe producție și cel de dezvoltare? Imaginează-ți o lume ideală în care toți dezvoltatorii lucrează pe aceeași platformă, în care platformele de dezvoltare și cele de producție au fost construite bazându-se pe aceleași specificații. Această lume există și se numește virtualizare. Vagrant este un tool de virtualizare, care are un răspuns la toate aceste întrebări, transformând această lume ideală într-o lume reală. Vagrant poate fi folosit pentru a crea și a configura medii de dezvoltare performante, portabile și reproductibile.

Vagrant este dezvoltat în Ruby de către Mitchell Hashimoto, Mithchell este un developer pasionat de automatizare. A început proiectul Vagrant în 2010, în timpul liber. În următorii ani proiectul a crescut și a început să fie folosit cu încredere de un număr tot mai mare de dezvoltatori. Ca urmare în 2012 Mitchell a înființat propria lui companie HashiCorp, pentru a dezvolta și pentru a oferi training-uri și suport profesional pentru Vagrant. În momentul de față Vagrant este un proiect open source, fiind rezultatul a sute de contribuitori pe GitHub.

Ce este Vagrant?

Pentru a realiza magia, Vagrant se bazează pe giganții lui, acționând ca un layer în fața providerilor VirtualBox, VMware, AWS sau alții. De asemenea, instrumente din industria-standard de provisionare, cum sunt script-urile Shell, Chef și Puppet pot fi folosite pentru a seta automat un nou mediu de dezvoltare. Vagrant poate fi folosit și în proiecte scrise în alte limbaje de programare cum ar fi PHP, Pyton, Java, C# sau JavaScript și poate fi instalat atât pe sisteme Linux, Mac OS X cât și pe Windows.

Vagrant oferă mașini virtuale tranzistorii, portabile care se pot muta dintr-o parte într-alta, fără a avea o locație fixă, exact ca un vagabond. Dacă ești programator, poți folosi Vagrant pentru a izola dependințele și configurațiile lor cu un singur mediu de dezvoltare consistent. Odată ce a fost creat Vagrantfile trebuie doar să rulezi comanda vagrant-uppentru ca totul să funcționeze pe mașina ta. Ca inginer de sistem, Vagrant îți oferă un mediu disponibil și un flux de muncă consistent pentru dezvoltarea și testarea script-urilor de management a infrastructurii. Poți testa foarte rapid shell script-uri, rețete Chef și module Puppet, folosind virtualizarea pe mașini locale cum sunt VirtualBox sau VMware. Apoi cu aceleași configurări, poți testa script-urile remote din cloudscum sunt AWS sau RackSpace, folosind același mod de lucru. Ca designer, folosind Vagrant poți să setezi cu ușurință un nou mediu de dezvoltare bazat pe fișierul de configurare Vagrantfile, care este deja configurat, fără să-ți mai faci griji cum să reconfigurezi aplicația.

Folosind Vagrant poți obține următoarele beneficii:

Vagrant — de la instalare până la mașina virtuală funcțională.

Înainte de a începe primul proiect Vagrant, trebuie să instalezi VirtualBox sau orice alt provider suportat de Vagrant. Pentru acest exemplu eu am folosit VirtualBox. Primul pas constă în instalarea instrumentului Vagrant, prin descărcarea și instalarea pachetului sau a executabilului corespunzător, de pe pagina oficială de descărcare a Vagrant-ului. Executabilul va adăuga automat vagrant în calea de sistem, fiind disponibil în terminal imediat după instalare, cum se poate vedea mai jos.

$ vagrant
Usage: vagrant [-v] [-h] command []
    -v, --version                    Print the version and exit.
    -h, --help                       Print this help.
Available subcommands:
     box          manages boxes: installation, removal, etc.
     destroy      stops and deletes all traces of the vagrant machine
     halt         stops the vagrant machine
     help         shows the help for a subcommand
     init         initializes a new Vagrant environment by creating a                    Vagrantfile
     package      packages a running vagrant environment into a box
     plugin       manages plugin s: install, uninstall, update, etc.
     provision    provisions the vagrant machine
     reload       restarts vagrant machine, loads new Vagrantfile              configuration
     resume       resume a suspended vagrant machine
     ssh          connects to machine via SSH
     ssh-config   outputs OpenSSH valid configuration to connect to 
           the machine
     status       outputs status of the vagrant machine
     suspend      suspends the machine
     up           starts and provisions the vagrant environment

Următorul pas este inițializarea instrumentului Vagrant în noul proiect, rulând comanda:

$ vagrant init

A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please 
Read the comments in the Vagrantfile as well as documentation on`vagrantup.com` for more information on using Vagrant.

După rularea acestei comenzi, Vagrantfile este generat automat în directorul proiectului pe care s-a făcut inițializarea. Vagrantfile este scris în Ruby, dar nu este necesară cunoașterea acestui limbaj de programare, deoarece majoritatea modificărilor constau într-o simplă atribuire de variabile. Fișierul Vagrantfile are următoarele roluri:

Selectarea scheletului pentru mașina virtuală

Fișierul Vagrantfile generat automat conține următoarele linii:

# Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "precise32"
  # The url from where the 'config.vm.box' box will be fetched if it
  # doesn't already exist on the user's system.
  config.vm.box_url = "http://files.vagrantup.com/precise32.box"

Directiva config.vm.box = "precise32", descrie tipul mașinii virtuale. "Box-ul" reprezintă scheletul pe care mașinile virtuale Vagrant vor fi construite. Acestea sunt fișiere portabile care pot fi folosite de către oricine, pe orice platformă pe care rulează Vagrant. Un lucru important de observat este faptul că aceste așa-zise cutii sunt create pe baza provider-ilor. În aces sens, înainte de instalarea scheletului mașinii virtuale, trebuie verficat provider-ul pe care se bazează. Pentru a selecta un schelet pentru mașina virtuală puteți accesa http://www.vagrantbox.es. Aici poate fi găsită o listă cu toate base-box-urile care sunt disponibile. Vagrant oferă de asemenea, posibilitatea de a crea "cutii" personalizare. Un instrument util în acest scop este Veewee**,care are ca obiectiv automatizarea tuturor pașilor pentru construirea acestor "cutii" cât și colectarea celor mai bune practici într-o modalitate transparentă.O "cutie"**poate fi adăugată și din linia de comandă tastând:

$ vagrant box add  

Unde poate fi definit oricum, singurul lucru de care trebuie ținut cont este ca parametrul respectiv să coincidă cu cel definit în cadrul directivei config.vm.box. reprezintă locația "cutiei". Poate fi calea spre un fișier local sau un HTTP URL spre o "cutie" externă.

$ vagrant box add precise32 http://files.vagrantup.com/precise32.box

Comenzile disponibile pentru management-ul "cutiilor" sunt listate mai jos.

$ vagrant box -h
Usage: vagrant box  []
Available subcommands:
    add
    list
    remove
    repackage
For help on any individual command run `vagrant box COMMAND -h`

For help on any individual command run vagrant box COMMAND -h

Selectarea provider-ului de virtualizare

Sunt două modalități care pot fi folosite pentru specificarea provider-ului, similar modului cum a fost descris mai sus pentru "cutii". O opțiune constă în specificarea provider-ului ca parametru din linia de comandă. Dacă este selectată această metodă, trebuie să te asiguri că argumentul prevăzut coincide cu cel specificat în directiva config.vm.provider.

$ vagrant up --provider=virtualbox

Celalaltă opțiune este să se specifice provider-ul doar în directiva config.vm.provider.

Important de menționat este faptul că unele "cutii" nu necesită specificarea provider-ului, deoarece pot funcționa cu orice tip de provider.

Configurarea parametrilor mașinei virtuale

Vagrantfile oferă posibilitatea configurării provider-ilor prin adăugarea directivei vb.customize. De exemplu, dacă vrei să mărești memoria alocată mașinii virtuale, poți să faci cum este descris mai jos.


# Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  config.vm.provider :virtualbox do |vb|
  # # Don't boot with headless mode
     #  vb.gui = true
     #
  # # Use VBoxManage to customize the VM. For example to change memory:
    vb.customize ["modifyvm", :id, "--memory", "2048"]
    end

Configurarea rețelei

Accesarea paginilor web în curs de dezvoltare, de pe mașina virtuală nu este o idee prea bună. Tocmai de aceea, Vagrant oferă funcționalități pentru configurarea rețelei, cu scopul de a accesa mașina virtuală, de pe mașina locală. Vagrantfile are trei directive care pot fi folosite pentru configurarea rețelei.

Traducând această directivă, deducem faptul că Apache-ul instalat pe mașina virtuală, creată de Vagrant, poate fi accesat de pe mașina locală, folosind url-ul http://127.0.0.1:8080. Aceasta înseamnă că tot traficul din rețea a fost redirecționat spre portul specificat pe mașina virtuală.


# Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  config.vm.provider :virtualbox do |vb|
  # # Don't boot with headless mode
     #  vb.gui = true
     #
  # # Use VBoxManage to customize the VM. For example to change memory:
    vb.customize ["modifyvm", :id, "--memory", "2048"]
    end
# Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  config.vm.network :public_network
 access to the machine
  # using a specific IP.
  config.vm.network :private_network, ip: "192.168.33.10"

Modificarea setărilor SSH

Vagrantfile oferă posibilitatea de a confgura namespace-ul config.ssh pentru a specifica numele utilizatorului, host-ul, port-ul, guest_port-ul, private_key_path, forward_agent, forward_x11 și shell.

Vagrant.configure("2") do |config|
  config.ssh.private_key_path = "~/.ssh/id_rsa"
  config.ssh.forward_agent = true
end

Sincronizarea directoarelor locale cu cele de pe mașina virtuală

În timp ce mulți utilizatori editează fișierele de pe mașinile virtuale folosind doar editoare sincronizate cu mașina virtuală prin SSH, Vagrant oferă posibilitatea de sincronizare automată a fișierelor de pe ambele mașini, folosind folder-ele sincronizate. În mod implicit Vagrant oferă posibilitatea da a sincroniza fișierele de pe mașina locală, cu directorul /vagrant de pe mașina virtuală. În acest fel directorul /vagrant localizat pe mașina virtuală, este identic cu cel de pe mașina locală. Astfel nu mai ești nevoit să folosești opțiunile de Upload și Download, din IDE pentru sincronizarea fișierelor. Dacă vrei să modifici directorul sincronizat de pe mașina sincronizată, o poți face adăugând următoarea directivă config.vm.synced_folder "../data", "/vagrant_data" în Vagrantfile.

Vagrant.configure("2") do |config|
    config.vm.synced_folder "../data", "/vagrant_data"
  end 

Provisionarea mașinii virtuale

Provizionarea nu este o sarcină de care se ocupă în mod normal dezvoltatorii, deoarece administratorii de sistem sunt cei care fac lucrurile acestea. Ideea de la care se pornește este de a înregistra configurările setate pe un server, cu scopul de a le replica pe un alt server. Mai demult administratorii de sistem țineau un wiki pentru comenzile rulate pe servere, dar această metodă nu era cea mai bună. Altă opțiune este să creezi back-up-uri .box sau .iso, astfel încât noile servere să poată fi configurate pe baza acestor fișiere. Dar, menținerea la zi a acestor fișiere este dificilă și necesită mai multă muncă, fiind în același timp destul de greu să sincronizezi toate serverele în acest fel. Provizionarea în zilele noastre, oferă posibilitatea de a adăuga software special pentru creare fișierelor de configurare, pentru executarea comenzilor, pentru crearea utilizatorilor sau managementul serviciilor, folosind sisteme moderne de provizionare. Vagrant poate fi integrat cu următoarele sisteme de provizionare: Shell, Ansible, Chef Solo, Chef Client, Puppet, Salt Stack. Cele mai populare sisteme de provizionare sunt Chef și Puppet, fiind menținute de o comunitate mare de utilizatori. Ambele sunt scrise în Ruby, având caracteristici similare cum ar fi modularizarea componentelor, pachete pentru instalarea software-ului sau template-uri pentru fișiere personalizate. Ambele sisteme sunt open source cu un model de plata pentru companii. O scurtă trecere în revistă pentru Puppet și Chef este prezentată în tabelul de mai jos.

Provizionarea cu Shell

Provizionare cu Shell în Vagrant este relativ ușoară. Poți scrie comenzi inline sau poți menționa calea spre script-ul shell. Calea poate fi spre un director local sau extern.

 Table 1. Chef vs. Puppet

Comandă inline

config.vm.provision :shell, :inline => "curl -L https://get.rvm.io | bash -s stable"

Cale fișier local

config.vm.provision :shell, :path => "install-rvm.sh", :args => "stable"

Cale fișier extern

config.vm.provision :shell, :path=>"https://example.com/install-rvm.sh", :args => "stable"

Provizionare cu Pupppet

Modulele Puppet pot fi descărcate de pe https://github.com/puppetlabs. Pe disc, un modul este un director care are structura prezentată în Fig. 1 Structura unui Modul Puppet. Programele Puppet se numesc "manifests" și folosesc extensia .pp pentru fișiere.

Manifestspot folosi tipuri variate de logică, cum sunt declarațiile condiționale, colecțiile de resurse, funcții pentru generarea textului, etc.. Dacă nu vrei să setezi manual fișierele manifest, există un instrument foarte simplu de utilizat cu GUI, care poate genera automat toate aceste fișiere. Acest instrument se numește PuPHPet.

Fig. 1 Structura unui Modul Puppet

Pentru a configura manual Vagrant cu Puppet, trebuie să setezi directivele Puppet după cum urmează:

config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "./tools/puppet/manifests/"
    puppet.module_path = "./tools/puppet/modules"
    puppet.manifest_file = "init.pp"
    puppet.options = ['--verbose']
End

unde init.pp este de forma

include mysql::server
class { '::mysql::server':
  root_password    => 'strongpassword'
}

MySql init.pp, din module/manifests/init.pp

class mysql::server (
   $config_file = $mysql::params::config_file,
   $manage_config_file = $mysql::params::manage_config_file,
   $package_ensure = mysql::params::server_package_ensure,
)

MySql params.pp, din module/manifests/params.pp

class mysql::params {

   $manage_config_file = true

   $old_root_password = ''

   $root_password = "strongpassword"

}

module/manifests/templates

Mysql Template
[client]
password=<%= scope.lookupvar('mysql::root_password') %>

Provizionarea cu Chef Solo

Rețetele Chef Solo pot fi descărcate de pe Opscode. Structura unei rețete Chef este prezentată în Fig. 2. Structura unei Rețete Chef Solo. Pentru adăugarea configurațiilor doar fișierele scrise îngroșat trebuie modificate.

Fig 2. Structura unei Rețete Chef Solo

Vagrantfile trebuie modificat după cum este afișat mai jos pentru a folosi Chef Solo ca provizionar.

config.vm.provision :chef_solo do |chef|
        chef.cookbooks_path = "cookbooks"
        chef.add_recipe "vagrant_main"
  #     chef.roles_path = "../my-recipes/roles"

  #     chef.data_bags_path = "../my-   recipes/data_bags"

  #     chef.add_role "web"
  #
  #   # You may also specify custom JSON attributes:
  #   chef.json = { :mysql_password => "foo" }
  end

unde vagrant_main are următoarea structură:

vagrant_main/recipes/default.rb

include_recipe "apache2"
include_recipe "apache2::mod_rewrite"

package "mysql-server" do
    package_name value_for_platform("default" => "mysql-server")
    action :install 
end

vagrant_main/templates/default.rb

NameVirtualHost *:80

    ServerAdmin webmaster@localhost
    ServerName cfratila.tsm.com
    ServerAlias www.cfratila.tsm.com 

    DocumentRoot /var/www

    
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    

    ErrorLog <%= node[:apache][:log_dir] %>/tsm-error.log
    CustomLog <%= node[:apache][:log_dir] %>/tsm-access.log combined
    LogLevel warn

Odată ce ai terminat configurarea fișierului Vagrantfile ești gata pentru crearea mașinii virtuale. Pentru acest pas trebuie să deschizi interfața în linia de comandă și să navighezi în folder-ul proiectului, unde ar trebui să fie fișierul Vagrantfile, pentru a sincroniza directoarele. Apoi rulează vagrant up pentru crearea și pornirea mașinii virtuale. Prima dată când vei rula vagrant up va dura puțin mai mult, pentru că Vagrant va descărca box-ul configurat. În exemplul meu, eu nu am adăugat box-ul rulând comanda vagrant box add , motiv pentru care box-ul va fi adăugat la rularea comenzii vagrant up.

D:projects    sm> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Box 'precise32' was not found. Fetching box from specified URL for  the provider 'virtualbox'. Note that if the URL does not have a box for this provider, you should interrupt Vagramt now and add the box yourself. Otherwise Vagrant will attempt to download the full box prior to discovering this error.
Downloading box from URL: http://files.vagrantup.com/precise32.box
Progres: 1% 

După ce a fost creată mașina virtuală, poți rula comanda vagrant sshpentru accesare. Pentru sistemele de operate Windows poți instala clientul PuTTY SSH, pentru accesarea mașinii virtuale, folosind credențialele de mai jos.

D:projects    sm> vagrant ssh
'ssh' executable not found in any directories in the %PATH% variable. Is an SSH client instaled? Try installing Cygwin, MinGW or Git, all of which contain SSH client. Or use the PuTTY SSH client with the following authentication information shown below:

Host: 127.0.0.1
Port: 2222
Username: vagrant
Private key: C:/Users/Carmen/.vagrant.d/insecure_private_key

Dacă ai modificat doar fișierele de provizionare și vrei să testezi rapid, poți rula doar vagrant provision sau vagrant --provision-with x,y,z, unde x,y,z reprezintă provizionarul :shell, de exemplu. Dacă vrei să salvezi statusul mașinii virtuale pentru a nu boot-a de fiecare dată, poți rula vagrant suspend. Cu vagrant resume se rezumă mașina Vagrant care a fost suspendată.

Procesul de lucru cu Vagrant nu este deloc complicat după cum reiese și din Fig.4. Doar rulând comanda vagrant up pe baza fișierului Vagrantfile, a configurațiilor setate pentru provizionar și provider, mașina virtuală este pornită și funcțională.

Mai sunt instrumente similare cu Vagrant?

Docker este un proiect open source folosit pentru ambalarea, tansportarea și rularea oricărei aplicații ca un container. Ideea de bază constă în crearea componentelor per aplicație. Componenta este de fapt un instantaneu al aplicației făcut la un moment dat. Dacă s-au modificat componentele, poți să faci commit la noul status al instantaneului, ceea ce face foarte ușoară procedura de roll back. Acest proiect, care este încă în stadiu de dezvoltare, sună promițător, pentru că nu folosește mașini virtuale cum funcționează Vagrant, ceea ce înseamnă că atât timpul de pornire cât și modalitatea de folosire a resurselor, sunt mai bune.

Fig 4. Vagrant – Procesul de lucru

Comparând metodologia de lucru a celor două instrumente, putem enunța următoarele afirmații:

  1. Vagrant este mai bun pentru că păstrează codul sursă și informațiile de deployment în același loc.
  2. Vagrant este mai bun pentru că este stabil și poate fi folosit în producție.
  3. Vagrant este mai bun pentru că poate fi integrat cu Linux, Windows și Mac OS X.
  4. Docker este mai bun pe parte de provizionare.
  5. Procedura de roll backeste mai rapidă și mai ușoară datorită sistemului de instantanee.
  6. Docker a venit cu un nou model de deployment.
  7. Docker poate fi integrat doar cu mașinile de Ubuntu.
  8. Docker nu este recomandat în producție deoarece este încă în faza de dezvoltare.

De ce merită să folosești Vagrant?

Eu sunt un developer focusat pe PHP nu administrator de sistem. Vreau să petrec cât mai mult timp codând. Nu vreau să îmi fac griji legate de setarea mediului de lucru sau de setarea mașinilor virtuale. Tot ceea ce vreau este să îmi setez mediul de lucru într-un mod cât mai simplu și cât mai rapid. Vreau să evit situațiile în care mașina mea virtuală crapă, și trebuie să o reconfigurez de la 0. De când folosesc Vagrant, nu îmi mai fac asemenea griji, pentru că există Vagrantfile, care îmi păstrează toate configurările. Sincronizarea directoarelor și redirecționarea traficului prin forwarding port sunt două caracteristici de care sunt foarte mulțumită. Pe când dezvoltam fără a folosi Vagrant, pierdeam mult timp așteptând să se sincronizeze fișierele de pe mașina mea locală, cu cele de pe mașina virtuală. Cu ajutorul directivei "synced folders" pot să lucrez în timp real pe mașina virtuală. În momentul de față, comunitatea de utilizatori Vagrant este în creștere, dar este posibil ca într-un timp relativ scurt, Docker să devină cel mai folosit instrument în comunitatea de dezvoltatori.

LANSAREA NUMĂRULUI 149

Marți, 26 Octombrie, ora 18:00

sediul Cognizant

Facebook Meetup StreamEvent YouTube

NUMĂRUL 147 - Automotive

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects