Permalink
Browse files

Global Code Review

  • Loading branch information...
ricofehr committed Jan 3, 2016
1 parent db051d9 commit 8cb89da1c6e020f87b2096b13bb530abbc683f0f
Showing with 853 additions and 717 deletions.
  1. +2 −2 LICENSE
  2. +61 −16 README.md
  3. +1 −1 client
  4. +1 −1 puppet
  5. +1 −1 ror/app/controllers/api/v1/branches_controller.rb
  6. +3 −6 ror/app/controllers/api/v1/brands_controller.rb
  7. +1 −1 ror/app/controllers/api/v1/commits_controller.rb
  8. +3 −6 ror/app/controllers/api/v1/frameworks_controller.rb
  9. +10 −11 ror/app/controllers/api/v1/groups_controller.rb
  10. +13 −13 ror/app/controllers/api/v1/projects_controller.rb
  11. +1 −1 ror/app/controllers/api/v1/sessions_controller.rb
  12. +4 −6 ror/app/controllers/api/v1/sshkeys_controller.rb
  13. +1 −1 ror/app/controllers/api/v1/systemimages_controller.rb
  14. +1 −1 ror/app/controllers/api/v1/systemimagetypes_controller.rb
  15. +4 −7 ror/app/controllers/api/v1/technos_controller.rb
  16. +13 −14 ror/app/controllers/api/v1/users_controller.rb
  17. +11 −15 ror/app/controllers/api/v1/vms_controller.rb
  18. +3 −5 ror/app/controllers/api/v1/vmsizes_controller.rb
  19. +2 −5 ror/app/controllers/application_controller.rb
  20. +11 −16 ror/app/helpers/projects_helper.rb
  21. +42 −40 ror/app/helpers/users_helper.rb
  22. +39 −42 ror/app/helpers/vms_helper.rb
  23. +3 −7 ror/app/models/branche.rb
  24. +1 −1 ror/app/models/brand.rb
  25. +10 −14 ror/app/models/commit.rb
  26. +1 −1 ror/app/models/framework.rb
  27. +3 −3 ror/app/models/group.rb
  28. +1 −1 ror/app/models/prefix_dns.rb
  29. +20 −26 ror/app/models/project.rb
  30. +1 −1 ror/app/models/project_techno.rb
  31. +1 −1 ror/app/models/project_vmsize.rb
  32. +24 −34 ror/app/models/sshkey.rb
  33. +1 −1 ror/app/models/systemimage.rb
  34. +1 −1 ror/app/models/systemimagetype.rb
  35. +1 −1 ror/app/models/techno.rb
  36. +23 −38 ror/app/models/user.rb
  37. +1 −1 ror/app/models/user_project.rb
  38. +25 −37 ror/app/models/vm.rb
  39. +1 −1 ror/app/models/vmsize.rb
  40. +1 −1 ror/app/serializers/branche_serializer.rb
  41. +1 −1 ror/app/serializers/brand_serializer.rb
  42. +2 −2 ror/app/serializers/commit_serializer.rb
  43. +1 −1 ror/app/serializers/framework_serializer.rb
  44. +2 −2 ror/app/serializers/group_serializer.rb
  45. +2 −2 ror/app/serializers/project_serializer.rb
  46. +1 −1 ror/app/serializers/sshkey_serializer.rb
  47. +2 −2 ror/app/serializers/systemimage_serializer.rb
  48. +3 −3 ror/app/serializers/systemimagetype_serializer.rb
  49. +1 −1 ror/app/serializers/techno_serializer.rb
  50. +6 −1 ror/app/serializers/user_serializer.rb
  51. +1 −1 ror/app/serializers/vm_serializer.rb
  52. +2 −2 ror/app/serializers/vmsize_serializer.rb
  53. +3 −3 ror/app/views/user_mailer/welcome_email.text.erb
  54. +5 −1 ror/config/application.rb
  55. +1 −1 ror/config/environments/development.rb.dist
  56. +1 −1 ror/config/environments/production.rb.dist
  57. +1 −1 ror/config/initializers/devise.rb
  58. +1 −1 ror/config/routes.rb
  59. +307 −101 ror/db/seeds.rb
  60. +42 −85 ror/lib/apiexternal/gitlabapi.rb
  61. +5 −7 ror/lib/apiexternal/osapi.rb
  62. +1 −1 ror/lib/exceptions.rb
  63. +1 −1 ror/public
  64. +1 −1 ror/sbin/files/import.sh
  65. +1 −1 ror/sbin/newproject
  66. +1 −1 scripts/inc/setup_debian
  67. +3 −3 scripts/inc/setup_fedora
  68. +2 −2 scripts/inc/setup_osx
  69. +1 −1 scripts/inc/utils
  70. +1 −1 scripts/setup
  71. +11 −11 scripts/setup-remote
  72. +1 −1 scripts/start
  73. +1 −1 scripts/stop
  74. +1 −1 vagrant/modules/pm/files/fw/fw_uosnv
  75. +1 −1 vagrant/modules/pm/files/varnish/default.vcl.3
  76. +2 −2 vagrant/modules/pm/files/varnish/default.vcl.ndc2
  77. +1 −1 vagrant/modules/pm/files/vsftpd/nextdeploy-addftp
  78. +3 −3 vagrant/modules/pm/manifests/base.pp
  79. +1 −1 vagrant/modules/pm/manifests/cron.pp
  80. +1 −1 vagrant/modules/pm/manifests/dnsmasq.pp
  81. +2 −2 vagrant/modules/pm/manifests/ftp.pp
  82. +1 −1 vagrant/modules/pm/manifests/fw.pp
  83. +1 −1 vagrant/modules/pm/manifests/gitlab7.pp
  84. +6 −6 vagrant/modules/pm/manifests/hids.pp
  85. +1 −1 vagrant/modules/pm/manifests/hosts.pp
  86. +1 −1 vagrant/modules/pm/manifests/http.pp
  87. +2 −2 vagrant/modules/pm/manifests/jenkins.pp
  88. +13 −13 vagrant/modules/pm/manifests/monitor.pp
  89. +1 −1 vagrant/modules/pm/manifests/openvpn.pp
  90. +15 −15 vagrant/modules/pm/manifests/os.pp
  91. +1 −1 vagrant/modules/pm/manifests/osclient.pp
  92. +5 −5 vagrant/modules/pm/manifests/phpcli.pp
  93. +5 −5 vagrant/modules/pm/manifests/postinstall.pp
  94. +3 −3 vagrant/modules/pm/manifests/pound.pp
  95. +1 −1 vagrant/modules/pm/manifests/puppet.pp
  96. +1 −1 vagrant/modules/pm/manifests/rabbit.pp
  97. +1 −1 vagrant/modules/pm/manifests/ror.pp
  98. +1 −1 vagrant/modules/pm/manifests/sql.pp
  99. +1 −1 vagrant/modules/pm/manifests/varnish.pp
  100. +3 −3 vagrant/modules/pm/manifests/w3af.pp
  101. +1 −1 vagrant/modules/roles/manifests/ndc2.pp
  102. +1 −1 vagrant/modules/roles/manifests/nextdeploy.pp
  103. +4 −4 vagrant/modules/roles/manifests/os.pp
  104. +1 −1 vagrant/nextdeploy/vagrantfiles/libvirt.dist
  105. +5 −5 vagrant/os/vagrantfiles/libvirt.dist
  106. +4 −4 vagrant/os/vagrantfiles/virtualbox.dist
View
@@ -1,4 +1,4 @@
Copyright (c) 2014-2015 Eric Fehr (Publicis Modem Company)
Copyright (c) 2014-2016 Eric Fehr (NextDeploy.io)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.
View
@@ -28,9 +28,9 @@ The REST api can be reached with 3 different ways
* /puppet Installation templates for the vms into the cloud. Customs class are included into puppet/pm folder, others are taken from puppetforge catalog. ([submodule](https://github.com/ricofehr/nextdeploy-puppet))
* /ror The rails application who serves the rest api.
* /ror/public The Webui developped on EmberJs ([submodule](https://github.com/ricofehr/nextdeploy-webui))
* /scripts Some jobs for setup completely the project in local workstation, start or stop nextdeploy
* /scripts Some jobs for setup completely the project in local workstation or remote servers
* /tmp Temporary folder
* /vagrant Definitions for create the 4 openstack nodes and the manager node
* /vagrant Definitions for create the 4 openstack nodes, the manager node and the monitoring node
## Submodules and Clone
The cli application (client folder), the webui (ror/public folder), the vm installation templates (/puppet folder) and some puppet modules of the community used by installation and setting of nextdeploy, are included in the project in the form of Submodules git.
@@ -59,10 +59,10 @@ Usage: ./scripts/./setup [options]
-c no destroy vm already created
-q quieter mode
-y ask yes to all question
-fs xxxx fileshare strategy for rails app source between host and nextdeploy node, nfs/rsync (Default is nfs)
-fs xxxx fileshare strategy for rails app source between host and nextdeploy node, nfs/rsync (Default is nfs for libvirt and virtualbox for vbox)
-cu xxxx cli username (default is usera@os.nextdeploy)
-cp xxxx cli password (default is word123123)
-g xxxx gitlaburi (default is gitlab.local)
-g xxxx gitlaburi (default is gitlab.nextdeploy.local)
-hv xxxx hypervisor: virtualbox or libvirt (default is virtualbox)
-nv enable nested virtualisation for nova (default is off), EXPERIMENTAL
-ne xxxx set the neutron external interface (for the public openstack subnet, default is eth2)
@@ -73,21 +73,57 @@ Usage: ./scripts/./setup [options]
-e xxxx subnet prefix for management network (default is 172.16.170)
-n xxxx dns server for vms (default is 192.168.171.60)
-m xxxx nextdeploy webui URI (default is nextdeploy.local)
-mc xxxx ndc2 default URI (default is ndc2.local)
-ma xxxx an email for some alerts purpose (default is admin@example.com)
-pa xxxx admin password used for some webui (like grafana)
-s xxxx nextdeploy dns suffixes (default is os.nextdeploy)
-r avoid change resolv.conf and hosts files
-vm start a vm after build is complete
```
Installation requires a large amount of RAM, a computer with 8GB of RAM minimum is required. Indeed, the OpenStack cloud is then implemented using vagrant through the creation of four virtual machines (controller, neutron, glance, nova) and another virtual machine is created to launch the rest app and hosts the gitlab and templates puppet installation. The script requires "curl" and "sudo" as a prerequisite.
Installation requires a large amount of RAM, a computer with 12GB of RAM minimum is required. Indeed, the OpenStack cloud is then implemented using vagrant through the creation of four virtual machines (controller, neutron, glance, nova) and another virtual machine is created to launch the rest app and hosts the gitlab and templates puppet installation. The script requires "curl" and "sudo" as a prerequisite.
The setup script has been tested on mac os x, debian, ubuntu and fedora. The hypervisor for nextdeploy installation is virtualbox (mac osx) or kvm (debian, ubuntu, fedora). Knowing that the performance of virtual machines deployed on OpenStack will be much better if nextdeploy is virtualized through kvm. Indeed, kvm can then itself be used as a hypervisor-level cloud. Otherwise (nextdeploy installation on virtualbox on macosx), it uses qemu.
The setup script has been tested on mac os x, debian, ubuntu and fedora. The hypervisor for nextdeploy installation is virtualbox (mac osx) or kvm (debian, ubuntu, fedora). Knowing that the performance of virtual machines deployed on OpenStack will be better if nextdeploy is virtualized through kvm.
A set of groups, users and projects are created during installation.
## Remote installation
For a remote installation, you must have 5 physical machines availabes: 4 for the cloud and 1 for nextdeploy manager. From this set, the following script makes much of the installation work and configuration based on puppet templates associated with vms vagrant.
For a remote installation, you must have at least 6 physical machines availables: 4 for the cloud, 1 for nextdeploy manager and 1 for the monitoring node. From this set, the following script makes much of the installation work and configuration based on puppet templates associated with vms vagrant.
```
./scripts/./setup-remote
Usage: ./scripts/./setup-remote [options]
-h this is some help text.
-q quieter mode
-y non-interactive mode, take default value when no setted
-g xxxx gitlaburi (default is gitlab.domain)
-ne xxxx set the neutron external interface (for the public openstack subnet, default is eth1)
-np xxxx set the neutron public interface (default is eth0)
-p xxxx subnet prefix (external network) for vms (default is 192.168.171)
-a xxxx subnet prefix for api network (default is 192.168.170)
-d xxxx subnet prefix for data network (default is 172.16.171)
-e xxxx subnet prefix for management network (default is 172.16.170)
-n xxxx dns server for vms (default is 192.168.171.60)
-m xxxx nextdeploy global URI (default is nextdeploy.domain)
-ma xxxx an email for some alerts purpose (default is admin@example.com)
-pa xxxx admin password used for some webui (like grafana)
-s xxxx nextdeploy dns suffixes (default is os.domain)
-t xxxx set the time needed to reboot a node (default is 220)
--domain xxxx global domain for the nextdeploy nodes (default is none)
--puppetmaster-ip xxxx ip for puppetmaster service
--puppetmaster-sshport xxxx port for ssh to puppermaster node (default is 22)
--puppetmaster-fqdn xxxx fqdn for puppetmaster service
--install-puppetmaster install the puppet master service
--update-puppetmaster update the puppet master service with lastest modules and hiera files
--controller-ip xxxx install the controller node (ip needed)
--neutron-ip xxxx install the neutron node (ip needed)
--glance-ip xxxx install the glance node (ip needed)
--nova-ip xxxx install the nova node (ip needed)
--nova2-ip xxxx install a second nova node (ip needed)
--nova3-ip xxxx install a third nova node (ip needed)
--nova4-ip xxxx install a fourth nova node (ip needed)
--nova5-ip xxxx install a fifth nova node (ip needed)
--nextdeploy-ip xxxx install the nextdeploy manager node (ip needed)
--ndc2-ip xxxx install the ndc2 node (ip needed)
```
## Groupes / Users
@@ -206,14 +242,23 @@ endpoint: nextdeploy.local
The ruby client manages the following commands
```
`ndeploy help` will print help about this command
`ndeploy up` launch current commit into vm
`ndeploy launch [projectname] [branch] [commit]` launch [commit] (default is head) on the [branch] (default is master) for [projectname] into remote nextdeploy
`ndeploy destroy` destroy current vm associated to this project
`ndeploy ssh` ssh into current vm
`ndeploy projects` list projects for current user
`ndeploy clone [project-name]` clone project in current folder
`ndeploy config [endpoint] [username] [password]` get/set properties settings for nextdeploy
` ndeploy clone [projectname] # clone project in current folder
` ndeploy config [endpoint] [username] [password] # get/set properties settings for nextdeploy
` ndeploy destroy # destroy current vm
` ndeploy getftp assets|dump [project] # get an assets archive or a dump for the [project]
` ndeploy git [cmd] # Executes a git command
` ndeploy help [COMMAND] # Describe available commands or one specific command
` ndeploy launch [projectname] [branch] [commit] # launch [commit] (default is head) on the [branch] (default is master) for [projectname] into remote nextdeploy platform
` ndeploy list # list launched vms for current user
` ndeploy listftp assets|dump [project] # list assets archive or a dump for the [project]
` ndeploy projects # list projects for current user
` ndeploy putftp assets|dump [project] [file] # putftp an assets archive [file] or a dump [file] for the [project]
` ndeploy ssh # ssh into remote vm
` ndeploy sshkey # Put your public ssh key (id_rsa.pub) onto NextDeploy
` ndeploy sshkeys # List sshkeys actually associated to the current user
` ndeploy up # launch current commit to remote nextdeploy
` ndeploy upgrade # upgrade ndeploy with the last version
` ndeploy version # print current version of ndeploy
```
The git repository for cli application: https://github.com/ricofehr/nextdeploy-cli
2 client
Submodule client updated 4 files
+1 −1 LICENSE
+4 −0 README.md
+1 −1 install.sh
+42 −82 nextdeploy.rb
@@ -2,7 +2,7 @@ module API
module V1
# Branche controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class BranchesController < ApplicationController
# Set branche object before show function
before_action :set_branche, only: [:show]
@@ -2,7 +2,7 @@ module API
module V1
# Brand controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class BrandsController < ApplicationController
# Hook who set brand object
before_action :set_brand, only: [:show, :update, :destroy]
@@ -15,18 +15,15 @@ def index
if @user.admin?
@brands = Brand.all
else
@brands = []
projects = @user.projects
if projects
@brands = [] << projects.map { |project| project.brand }
@brands.flatten! if @brands.flatten
@brands.uniq!
@brands = projects.flat_map(&:brand).uniq
end
end
# Json output
respond_to do |format|
format.json { render json: @brands, status: 200 }
format.json { render json: @brands || [], status: 200 }
end
end
@@ -2,7 +2,7 @@ module API
module V1
# Commit controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class CommitsController < ApplicationController
# set commit object before show function
before_action :set_commit, only: [:show]
@@ -4,7 +4,7 @@ module V1
# Actually, framework objects are managed directly in database.
# Controller is needed only for display properties into json format for rest compliance
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class FrameworksController < ApplicationController
# set framework object before show function
before_action :set_framework, only: [:show]
@@ -15,18 +15,15 @@ def index
if @user.admin?
@frameworks = Framework.all
else
@frameworks = []
projects = @user.projects
if projects
@frameworks = [] << projects.map { |project| project.framework }
@frameworks.flatten! if @frameworks.flatten
@frameworks.uniq!
@frameworks = projects.flat_map(&:framework).uniq
end
end
# Json output
respond_to do |format|
format.json { render json: @frameworks, status: 200 }
format.json { render json: @frameworks || [], status: 200 }
end
end
@@ -2,7 +2,7 @@ module API
module V1
# Group controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class GroupsController < ApplicationController
# Hook who set group object
before_action :set_group, only: [:show, :update, :destroy]
@@ -15,24 +15,23 @@ def index
if @user.admin?
@groups = Group.all
else
@groups = []
if @user.lead?
@groups = [] << @user.group
@groups << @user.group
projects = @user.projects
if projects && projects.length > 0
users = projects.map { |project| project.users }
users.flatten! if users.flatten
users.uniq!
users.select! { |u| ! u.admin? }
@groups = users.map { |usr| usr.group }
@groups.uniq!
if projects && projects.size > 0
users = projects.flat_map(&:users).uniq
users.select! { |u| !u.admin? }
@groups << users.map(&:group)
end
else
@groups = [] << @user.group
@groups << @user.group
end
end
respond_to do |format|
format.json { render json: @groups }
format.json { render json: @groups.flatten.uniq }
end
end
@@ -2,7 +2,7 @@ module API
module V1
# Project controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class ProjectsController < ApplicationController
# Hook who set project object
before_action :set_project, only: [:update, :destroy]
@@ -43,7 +43,7 @@ def index
# Display details about one project
def show
if ! params[:id].eql?('0')
if params[:id] != '0'
@project = Project.includes(:users).includes(:technos).find(params[:id])
# Json output
respond_to do |format|
@@ -64,8 +64,8 @@ def check_name
name = params[:name]
proj_id = params[:id].to_i
# check if the name is already taken by other projects
Project.all.each do |proj|
valid = false if proj.id != proj_id && proj.name.eql?(name)
Project.all.each do |proj|
valid = false if proj.id != proj_id && proj.name == name
end
(valid) ? (codestatus = 200) : (codestatus = 410)
render nothing: true, status: codestatus
@@ -104,7 +104,7 @@ def show_by_gitpath
# Import a new project object from a git path
def import
gitpath_import = params['gitpath'] ;
params['gitpath'] = params['gitpath'].gsub('^.*:[0-9]+','') ;
params['gitpath'] = params['gitpath'].sub(/^.*:[0-9]+/, '') ;
end
# Create a new project object
@@ -146,7 +146,7 @@ def destroy
private
# check right about admin user
def only_create
if ! @user.project_create?
if ! @user.is_project_create
raise Exceptions::GitlabApiException.new("Access forbidden for this user")
end
@@ -168,13 +168,13 @@ def set_project
def ember_to_rails
params_p = params[:project]
params_p[:techno_ids] = params_p[:technos]
params_p[:user_ids] = params_p[:users]
params_p[:owner_id] = params_p[:owner]
params_p[:framework_id] = params_p[:framework]
params_p[:brand_id] = params_p[:brand]
params_p[:systemimagetype_id] = params_p[:systemimagetype]
params_p[:vmsize_ids] = params_p[:vmsizes]
params_p[:techno_ids] ||= params_p[:technos]
params_p[:user_ids] ||= params_p[:users]
params_p[:owner_id] ||= params_p[:owner]
params_p[:framework_id] ||= params_p[:framework]
params_p[:brand_id] ||= params_p[:brand]
params_p[:systemimagetype_id] ||= params_p[:systemimagetype]
params_p[:vmsize_ids] ||= params_p[:vmsizes]
# permit empty user_ids array if we want disable all users
params_p[:user_ids] ||= []
@@ -2,7 +2,7 @@ module API
module V1
# Session controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class SessionsController < Devise::SessionsController
before_filter :authenticate_api_v1_user!, :except => [:create]
# Json output
@@ -2,7 +2,7 @@ module API
module V1
# Sshkey controller for the rest API (V1).
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class SshkeysController < ApplicationController
# Hook who set sshkey object
before_action :set_sshkey, only: [:show, :update, :destroy]
@@ -14,10 +14,8 @@ class SshkeysController < ApplicationController
# List all sshkey for one user
def index
# Default: current user
user_id = @user.id
# Other user if parameter sended
user_id = params[:user_id] if params[:user_id]
(params[:user_id]) ? (user_id = params[:user_id]) : (user_id = @user.id)
@sshkeys = User.includes(:sshkeys).find(user_id).sshkeys
# Json output
@@ -74,10 +72,10 @@ def destroy
# houuuu que c est moche
def ember_to_rails
params_p = params[:sshkey]
params_p[:user_id] = params_p[:user] unless params_p[:user_id]
params_p[:user_id] ||= params_p[:user]
params_p.delete(:user)
# normalize name value
params_p[:name].gsub!(/[^a-zA-Z-]/,'')
params_p[:name].tr!('^a-zA-Z-', '')
params[:sshkey] = params_p
end
@@ -4,7 +4,7 @@ module V1
# Actually, Systemimage objects are managed directly in database.
# Controller is needed only for display properties into json format for rest compliance
#
# @author Eric Fehr (eric.fehr@publicis-modem.fr, github: ricofehr)
# @author Eric Fehr (ricofehr@nextdeploy.io, github: ricofehr)
class SystemimagesController < ApplicationController
# set systemimage object before show function
before_action :set_systemimage, only: [:show]
Oops, something went wrong.

0 comments on commit 8cb89da

Please sign in to comment.