Skip to content
Snippets Groups Projects
Commit 8249e632 authored by Sid Sijbrandij's avatar Sid Sijbrandij
Browse files

Merge branch 'master' of gitlab.com:gitlab-com/www-gitlab-com

parents e2fdffbf 4716ff0b
No related branches found
No related tags found
No related merge requests found
Pipeline #
Loading
Loading
@@ -32,9 +32,7 @@ We're still working to improve this demo further, please see [all open idea-to-p
- TOC
{:toc}
 
## Set up
### Installation
## GitLab installation
 
There are three options:
 
Loading
Loading
@@ -42,6 +40,8 @@ There are three options:
1. [Set up a cluster on Google Container Engine (GKE)](gke-setup)
1. [set up a cluster on Azure Container Service (ACS)](acs-setup)
 
## Project set up
### Cleanup
 
> * Delete previous GitLab groups you made last time
Loading
Loading
@@ -102,7 +102,7 @@ Now we’re ready to configure GitLab Auto Deploy. Auto Deploy is the easiest wa
 
Great, that completes our setup. As you can see, our first pipeline is now running. This will build our project, create a container image using our Dockerfile, and then deploy to staging. Let's go ahead and trigger a deploy to production as well, so our app is fully deployed.
 
## Project permissions (optional: only show if project permissions are important)
### Project permissions (optional: only show if project permissions are important)
 
Okay, so everything we need to bring an application from idea to production is set up. But let's assume you want to safeguard your source code before handing this over to your developers. I'll take you through a few key ways you can outline project permissions and manage your team's workflows.
 
Loading
Loading
@@ -118,7 +118,9 @@ Okay, so everything we need to bring an application from idea to production is s
 
Permissions, merge request approvals, and protected branches help you build quality control into your development process so you can confidently hand GitLab over to your developers to get started on turning their ideas into a reality.
 
## Idea (Chat)
## Idea to Production
### Idea (Chat)
 
Let's go to our Mattermost client. Today, more of your team's conversations are happening in chat, not in issues. With GitLab's chat command integration, you can easily turn ideas into issues.
 
Loading
Loading
@@ -141,13 +143,13 @@ Great. Now we can see what commands are available. Let's go ahead and create an
> Currently it is just Hello World.
> ```
 
## Issue (Tracker)
### Issue (Tracker)
 
Great, we’ve created a new issue, directly from chat, and now we can click through to see our first issue on our new project.
 
> * Click on the link that starts with #1
 
## Plan (Board)
### Plan (Board)
 
Inspiration is perishable, so let's pick this one up right away. As a team lead or manager, I'd go to the Issue Board.
 
Loading
Loading
@@ -164,7 +166,7 @@ There. Now we can just add the new issue from the backlog into the Doing column,
> * Click on `Add 1 issue`
> * Drag issue from `To Do` to `Doing`
 
## Commit (Repo)
### Commit (Repo)
 
Now let’s get coding! I'll head over to the Repository and start editing.
 
Loading
Loading
@@ -186,7 +188,7 @@ And now it gives me an option to create a Merge Request, how nice of it. Let's g
> * `Submit merge request`
> * If popup asks to show notifications, click Allow.
 
## Build (CI)
### Build (CI)
 
As soon as the Merge Request is created, we see it automatically kicked off the CI/CD Pipeline that will test our contributed code.
 
Loading
Loading
@@ -197,7 +199,7 @@ Here we see a simple pipeline that contains 3 stages for build, review, and clea
 
> * Click on Build
 
## Runner progress (optional: only show if CI/CD is taking a while)
### Runner progress (optional: only show if CI/CD is taking a while)
 
While it’s running, we can head back to our Kubernetes console to see that our GitLab Runner is working directly with Kubernetes to spawn new containers for each job, as they are needed. It even creates a namespace for the project, providing isolation.
 
Loading
Loading
@@ -207,9 +209,9 @@ While it’s running, we can head back to our Kubernetes console to see that our
> * Change the Namespace drop-down to `minimal-ruby-app`
> * Click on Pods
 
## Review (MR)
### Review (MR)
 
### Review apps
#### Review apps
 
And in the Review step, we deploy that image to a temporary review app in our Kubernetes cluster.
> * Go Back
Loading
Loading
@@ -225,7 +227,7 @@ Right from the merge request, we see a new status telling us that it’s been de
 
So this is what we just changed, and any new changes pushed to our branch will automatically update the app.
 
### Debugging (Terminal)
#### Debugging (Terminal)
 
Now if there were any problems, for example differences between development and production, and you don't want to keep testing changes by pushing them to source control, we could debug those problems right here. By clicking the web terminal button we get a command prompt in the same container as our application.
 
Loading
Loading
@@ -250,7 +252,7 @@ And now we can view the web page live to see how we like the changes.
 
> * Click external URL link on top right (2nd from right)
 
### Code review
#### Code review
 
At this point we'd usually ask for another developer on the team to review our merge request. They can see the exact code that has changed, comment on it, and we'd see a thread of the discussion, as well as get an email notification, of course.
 
Loading
Loading
@@ -259,13 +261,13 @@ At this point we'd usually ask for another developer on the team to review our m
> * Comment "Looks good", Submit
> * Go to Discussion tab to see comment
 
### Merge to `master`
#### Merge to `master`
 
This all looks great so let’s click the Accept Merge Request button to merge the changes into the master branch.
 
> * Click Accept Merge Request
 
## Canary
### Canary
 
Now let’s ship these changes to production! But we’re not going to ship directly to the entire production fleet. In GitLab 9.1, we added support for canary deploys, basically letting you deploy to a smaller portion of your fleet to reduce risk.
 
Loading
Loading
@@ -274,7 +276,7 @@ Now let’s ship these changes to production! But we’re not going to ship dire
 
But now we see something interesting, I’ll keep hitting refresh, and you can see that half the time, I get the new canary code, and half the time I get the old code.
 
## Production
### Production
 
When we’ve validated that the canary is working as expected, we can then go and ship it completely to production. Let's go ahead and do that now.
 
Loading
Loading
@@ -285,7 +287,7 @@ Great, here we see the deploy to production happening live.
 
> * wait until it is done
 
### Environments with deployment history
#### Environments with deployment history
 
Now that it is done let’s go back to Environments.
 
Loading
Loading
@@ -297,7 +299,7 @@ Ok great, we now see the production deploy happened less than a minute ago.
 
There we go! We've got our new text in it; all the way from idea to production!
 
## Deploy Boards (optional: only show if you demoing GitLab EE)
### Deploy Boards (optional: only show if you demoing GitLab EE)
 
Here's a new feature in 9.0, if I click on this icon, I see the deploy board for `production`. Right now it's only showing a single pod, but a new tweak to Auto Deploy is that I can control the size of my production fleet using project variables. Here I’ll set `PRODUCTION_REPLICAS` to 10 and redeploy.
 
Loading
Loading
@@ -308,14 +310,14 @@ Here's a new feature in 9.0, if I click on this icon, I see the deploy board for
 
And here we can see fleet get rolled out in realtime.
 
## Monitoring
### Monitoring
 
So that's a high level status of the deploy, but how about monitoring the ongoing health of your app environments? Clicking on the graph icon, I see a graph of CPU and memory usage, taken from the built-in Prometheus monitoring, the leading open-source monitoring solution. There’s not much to see right now, but this will show the last 8 hours so you can monitor how your app is doing. Application performance monitoring can help your team be more strategic, preventing errors vs. simply reacting to them. Imagine if your application monitoring tool could help you avoid pushing poor-performing code in the first place, saving your business future downstream costs? That's exactly where we are heading.
 
> * Go to Environments > `staging`
> * Click on graph icon
 
### Merge request monitoring
#### Merge request monitoring
 
Going back to the merge request...
 
Loading
Loading
@@ -323,7 +325,7 @@ Going back to the merge request...
 
We now see another status showing that this code has indeed been deployed to staging and production. In GitLab 9.2, we introduced app performance feedback, right on the merge request, and in 9.3, we made it even better, telling you how much your memory usage changed before and after the merge request was deployed.
 
## Feedback (Cycle Analytics)
### Feedback (Cycle Analytics)
 
To help you spot bottlenecks in your development process, GitLab has a built-in dashboard that tracks how long it takes the team to move from idea to production.
 
Loading
Loading
@@ -333,7 +335,7 @@ Here we can see some metrics on the overall health of our project, and then a br
 
This is great for team managers and high level managers looking to better understand their company's release cycle time, which is key to staying competitive and responding to customers.
 
### Instance Monitoring (optional)
#### Instance Monitoring (optional)
 
Not only does Prometheus monitor your apps, but it monitors the GitLab instance itself. Let's go to the Prometheus dashboard.
 
Loading
Loading
@@ -348,18 +350,17 @@ And then memory usage:
 
> * Copy `(1 - ((node_memory_MemFree + node_memory_Cached) / node_memory_MemTotal)) * 100` into the Expression Bar; hit enter.
 
## Conclusion
### Conclusion
 
So that's it. In less than 10 minutes, we took an an idea through issue tracking, planning with an issue board, committing to the repo, testing with continuous integration, reviewing with a merge request and a review app, debugging in the terminal, deploying to production, application performance monitoring, and closing the feedback loop with cycle analytics dashboard. This all on top of a container scheduler that allows GitLab, the GitLab Runners for CI, and the applications we deploy to scale. Welcome to GitLab, the only platform that connects every step of your software development lifecycle, helping you bring modern applications from idea to production, quickly and reliably.
 
## CI/CD Deep Dive
 
Let's now switch over to the last portion of our demonstration, a more in-depth look at GitLab CI. Let's start at the top with Installation, Configuration, and Integration.
 
## Part 1: Installation / Configuration / Integration
### Part 1: Installation / Configuration / Integration
 
## Part 1.2: Connecting to a Repository (Mirroring)
### Part 1.2: Connecting to a Repository (Mirroring)
 
Now, we've already installed GitLab earlier so we will continue on to connecting to our source code repository. We'll get started with a simple Java app which is composed to two parts. A Java library and a front-end service which depends on that library to respond to users with a simple greeting.
 
Loading
Loading
@@ -367,7 +368,7 @@ Now the vast majority of our customers utilize our built-in Git repository funct
 
> Project Settings > Repository > Pull from a remote repository
 
## Part 1.3: Agile Planning Tools (JIRA & Slack)
### Part 1.3: Agile Planning Tools (JIRA & Slack)
 
Similar to the repository, GitLab also includes a powerful set of planning tools as you saw earlier. However we also have great integration options here as well, for example tools like JIRA and Slack. Let's see how these are configured now.
 
Loading
Loading
@@ -379,9 +380,9 @@ Enabling Slack notification and ChatOps commands are similarly easy. For notific
 
> Project Settings > Integrations > Notifications
 
## Part 1.4 & 1.5: Downstream Tool Integration
### Part 1.4 & 1.5: Downstream Tool Integration
 
### Working with `.gitlab-ci.yml` (Versioned, Ways to edit)
#### Working with `.gitlab-ci.yml` (Versioned, Ways to edit)
 
> Repository
 
Loading
Loading
@@ -393,7 +394,7 @@ Before we do that though, let's just talk briefly about the various options for
 
Let's now take a closer look at this file, and how you can define the pipeline and integrate with a wide variety of tools.
 
### Settings in `.gitlab-ci.yml` (Self Service, Bash script, Templates)
#### Settings in `.gitlab-ci.yml` (Self Service, Bash script, Templates)
 
> Click on the `.gitlab-ci.yml` file to view
 
Loading
Loading
@@ -406,13 +407,13 @@ Next we start to define our stages and jobs. In GitLab CI, a Pipeline is made up
 
The stages keyword defines the order the stages should execute in. So our flow will be: build, test, generate docs, release, and then deploy.
 
#### Build Stage (Maven, Bash scripting)
##### Build Stage (Maven, Bash scripting)
 
We then start to define our jobs, or the actions we want to perform.
 
Our first job is to build our library, which we utilize Maven for. You can see here we simply invoke Maven, as we would as if we were running it on our machines. That is because the script you see here, is actually just Bash script. This means provides a great amount of flexibility, because you can now automate anything you would normally do on your machine.
 
#### Test Stage (2 jobs, code coverage output, artifacts)
##### Test Stage (2 jobs, code coverage output, artifacts)
 
After we built our library, we are now going to test it.
 
Loading
Loading
@@ -420,15 +421,15 @@ Our test stage includes two jobs: unit tests and static analysis with codeclimat
 
While that is happening, we will also be running static analysis with codeclimate. Here we override the default image, to utilize the official Docker image. We use that to then execute run Docker-in-Docker, to execute the codeclimate image to analyze our source. Once that is done, we retrieve our JSON report and persist it as an artifact.
 
#### Doc Stage (Generate, Artifacts)
##### Doc Stage (Generate, Artifacts)
 
Here, we are simply generating our javadocs and again retaining them as an artifact.
 
#### Release Stage (Protected Variable)
##### Release Stage (Protected Variable)
 
Now that we have tested our library, we are ready to release it. We are going to use Maven again to publish our library out to our Packagecloud server. Now you are paying really close attention, you will see we are using a variable we did not define above. That is because this is a credential token, which should not be checked into the repository. Instead, we add this as a Protected Variable in our project settings, which only administrators can view.
 
#### Deploy Stage (Pages, Cross-Project Pipelines)
##### Deploy Stage (Pages, Cross-Project Pipelines)
 
Finally we have our last stage, deploy, which includes two jobs.
 
Loading
Loading
@@ -436,7 +437,7 @@ Our Pages job is slightly unique, in that this is a special job that works in ta
 
Now for our last job, recall that our Java app was made up of two components: this library and a front-end service which used it. At this point, we've confirmed that all of our tests pass which is a good start. But what about downstream projects that utilize this? Well this job kicks off what we refer to as a cross-project pipeline. We take a stock alpine image, install curl, then use it to trigger the webhook to start a new front-end service pipeline. Confirming downstream projects are not negatively affected by upstream changes is as easy as a few lines of YML.
 
#### Wrap up (Self-service: no plugins, Flexible: Bash script, Templates)
##### Wrap up (Self-service: no plugins, Flexible: Bash script, Templates)
 
As you can see, this supports our larger goals of GitLab: scalability, flexibility, and self-service:
* A developer can integrate with any tool they need, without having to worry about installing plugins or involving the administrators. They can simply provide the required container or VM to run the script in, or install their own runner on a machine with the associated requirements.
Loading
Loading
@@ -444,7 +445,7 @@ As you can see, this supports our larger goals of GitLab: scalability, flexibili
* Moving into the Part 2, you can see integrating with build automation tools like Maven is similarly straight forward and easy.
* We also have a collection of templates which can be used to help users get started.
 
## Deep Tool Integration & Intelligence (Code Climate, Code Coverage, JIRA Issue)
### Deep Tool Integration & Intelligence (Code Climate, Code Coverage, JIRA Issue)
 
Now we already showed how easy it is to execute unit tests and static analysis, but our integration goes a lot deeper. I have a pending merge request ready for me to review, so lets take a look.
 
Loading
Loading
@@ -464,7 +465,7 @@ Now similar to what you saw earlier with our integrated issue feature, we will g
 
> Show it is already linked, and now closed. Close tab and return to MR.
 
## Part 2.6, 2.7: Executing Build Automation "Happy Path"
### Part 2.6, 2.7: Executing Build Automation "Happy Path"
 
By committing a change to master we now have a new pipeline running, let's take a look to see how it executes and the notifications we receive.
 
Loading
Loading
@@ -472,7 +473,7 @@ By committing a change to master we now have a new pipeline running, let's take
 
We can now see a real-time overview of our pipeline as it progresses. Our build stage just completed, and you can now see our two test stages executing in parallel. Let's take a closer look at our unit test job.
 
### Unit Tests Job (Maven, Code Coverage, Artifacts)
#### Unit Tests Job (Maven, Code Coverage, Artifacts)
 
> Click on `unit tests` job
 
Loading
Loading
@@ -480,7 +481,7 @@ As before, we get a live look at the build log as it is being executed by our Ru
 
Now lets go take a look at our codeclimate job.
 
### Code Climate Job (Docker, Artifact)
#### Code Climate Job (Docker, Artifact)
 
> Click on `codeclimate` job from right pane
 
Loading
Loading
@@ -492,7 +493,7 @@ Going back to pipeline overview, we can check back in on our progress.
 
Our release job is now executing, lets see how that is progressing.
 
### Release Job (Protected Variable, Maven deploy)
#### Release Job (Protected Variable, Maven deploy)
 
We're now ready to publish our library to our packagecloud server, utilizing Maven. Remember we had utilized a protected variable for this, to ensure the secure token was not committed into the repository.
 
Loading
Loading
@@ -502,11 +503,11 @@ And that's it, our library is now available for others to consume.
 
> Go back to pipeline overview
 
### Pages Job (as necessary to kill time for success notifications)
#### Pages Job (as necessary to kill time for success notifications)
 
In our Pages job, we are leveraging the artifacts we collected in our earlier stages. Here, we simply copy them into a single directory structure, and persist them in a new artifact. Our GitLab Pages service will take over from here to copy our code coverage and documentation to our static site. Note this works with any static site generator as well.
 
### Notifications - Success: Browser, Slack, Favicon, Email (off by default)
#### Notifications - Success: Browser, Slack, Favicon, Email (off by default)
 
As you can see from our notifications, our pipeline has succeeded. In the event you didn't catch them all during that flurry, let's review them quickly:
 
Loading
Loading
@@ -520,7 +521,7 @@ As you can see from our notifications, our pipeline has succeeded. In the event
 
> Go back to pipeline overview
 
### Cross Project Pipelines (Environment Screen, Registry)
#### Cross Project Pipelines (Environment Screen, Registry)
 
Let's check in and see how our Cross Project Pipeline is doing, for our front-end service.
 
Loading
Loading
@@ -545,7 +546,7 @@ Here we can a view of all the containers we currently have pushed to the registr
 
Aside from an integrated UI, one of the key benefits of integrating the registry is the unified authentication architecture. Instead of having to manage credentials and security on your own, GitLab makes is extremely simple. We provide a simple environment variable to access the registry which is valid for as long as the job is executing.
 
### Happy Path Wrap-up (Self-Service, Easy)
#### Happy Path Wrap-up (Self-Service, Easy)
 
So that's our "Happy Path". With just a few lines of YML we accomplished:
 
Loading
Loading
@@ -557,7 +558,7 @@ So that's our "Happy Path". With just a few lines of YML we accomplished:
 
And we did that all without involving a single administrator or opening a ticket. Completely self-service by a developer.
 
## Part 2.7: Negative Path
### Part 2.7: Negative Path
 
But you know what? I think we can make some improvements to library.
 
Loading
Loading
@@ -569,7 +570,7 @@ We'll commit this to a new branch, and create a new Merge Request.
 
> Commit to new branch, create new Merge Request
 
### Failed Merge Request (MWPS, GitLab, Slack, Favicon, Email)
#### Failed Merge Request (MWPS, GitLab, Slack, Favicon, Email)
 
Since we've now created a new branch, we have a new Pipeline running. Now if I am the reviewer and already checked out the changes, I can simply click on our Merge When Pipeline Succeeds button. This will automatically merge the issue as long as we have a good pipeline. However if it fails and the developer needs to make a further change, it will of course have to get re-reviewed. This is a great way to save some time for your reviewers, so they aren't waiting until a pipeline complete before moving on.
 
Loading
Loading
@@ -588,7 +589,7 @@ Let's quickly review the notifications we received:
> Show email tab
> Return to the job log for the unit tests
 
#### Failed Merge Follow up (New Issue, New Todo)
##### Failed Merge Follow up (New Issue, New Todo)
 
As you can see we have a wide array of ways to inform users that their pipeline failed. But what is at least as important as notifications, is follow up.
 
Loading
Loading
@@ -600,7 +601,7 @@ This way it's easier for users to go about their day, with less repetition, and
 
So that's a quick review of what we would call the "Negative Path". Let's take a look at some of the available options for reviewing pipeline performance.
 
## Part 2.8: GUI (Pipeline History, CI Monitoring with Prometheus)
### Part 2.8: GUI (Pipeline History, CI Monitoring with Prometheus)
 
> Click on Pipelines tab for the list of all pipelines
 
Loading
Loading
@@ -614,7 +615,7 @@ But that isn't the only way to get an understanding of what the CI system is cur
 
Here you can see a wide variety of metrics, ranging from the number of jobs in each state to how many parallel jobs a given runner is executing. All of these metrics are available not just on SaaS, but also self-hosted as well.
 
## Part 2.9: Analytics (Charts, Static Analysis, Code Coverage, Cycle Analytics, APM)
### Part 2.9: Analytics (Charts, Static Analysis, Code Coverage, Cycle Analytics, APM)
 
GitLab also provides methods to understand the health of the CI pipelines for a particular job as well. We have a dedicated analytics page we call Charts, which shows additional information for each project.
 
Loading
Loading
@@ -629,7 +630,7 @@ And we've already taken a look at some of the other analytics services we offer
* Code coverage parsing
* and cycle analytics, for team health and efficiency.
 
## GitLab Runner (Shared, Specific, Autoscaling)
### GitLab Runner (Shared, Specific, Autoscaling)
 
Now I've mentioned our Runner a couple times, but we've been mostly looking at what it can do. Next we'll take a few minutes to talk about this important part of GitLab CI.
 
Loading
Loading
@@ -639,7 +640,7 @@ In our Pipelines settings page is where a developer can take a look at the Runne
 
> Go to Settings, Pipelines.
 
### Shared (Ease, Speed, Efficient Management)
#### Shared (Ease, Speed, Efficient Management)
 
Shared runners are runners that have been provided by the administrators of the GitLab instance, we've been using one as you can see here. By allowing administrators to provide a shared pool, there are a number of benefits:
 
Loading
Loading
@@ -648,7 +649,7 @@ Shared runners are runners that have been provided by the administrators of the
 
But there are some cases where an administrator has not provided a shared pool, or they don't meet your needs.
 
### Specific (Self-service, Install)
#### Specific (Self-service, Install)
 
For these cases, we have the ability for any development team to connect their own Runners. They simply download the Runner, enter in the URL and key, and they are on their way.
 
Loading
Loading
@@ -663,7 +664,7 @@ During registration we configure a few aspects of the runner:
* Name that we want to appear on the Runner page
* And then any tags we want to specify. Tags allow you to uniquely identify runners with certain properties, for example here we can set `osx` and `xcode8` to identify what we have installed. These are then specified in the `.gitlab-ci.yml` for the job you want to run.
 
### Runner Architecture (Shell/SSH, VM, Container, Auto-Scaling)
#### Runner Architecture (Shell/SSH, VM, Container, Auto-Scaling)
 
The last choice we have to make is what mode of operation we want for our Runner. The simplest is Shell, where it executes the script on the machine and account it's installed on.
 
Loading
Loading
@@ -671,7 +672,7 @@ We then have support for working with images and containers, via virtual box, pa
 
The last mode of operation, is what we call our auto-scaling runner. We support this on Docker Machine and Kubernetes, and here the Runner will elastically process jobs as needed to process the CI queue.
 
### Wrap up (Self-service, Flexible, Scalable)
#### Wrap up (Self-service, Flexible, Scalable)
 
And that's it for the configuration, we just start our Runner and it is ready to process jobs.
 
Loading
Loading
@@ -686,12 +687,12 @@ Second it provides a lot of flexibility. If you need to run jobs on an ARM devic
 
Last, is scalability. A handful of auto-scaling runners on GitLab.com routinely process over a thousand concurrent CI jobs. If more are needed, simply turn up the dial.
 
## Discuss GitLab Pages
### Discuss GitLab Pages
 
The final area we wanted to discuss was GitLab Pages. As you saw earlier with our Pipeline, we pushed our javadocs and code coverage reports here with just a few lines of YML. This site is then hosted by GitLab at a specific domain, making it easy to publish technical content or even entire websites with CI.
 
# Wrap (Flexible: Bash it, script it. Self-service: runners, no plugins. Scalable: SaaS scale CI)
### Wrap (Flexible: Bash it, script it. Self-service: runners, no plugins. Scalable: SaaS scale CI)
 
To summarize, GitLab CI is flexible. If you can bash it, you can automate it. Self service runners, no plugins. And SaaS scale CI, with auto-scaling.
 
Questions?
Questions?
Loading
Loading
@@ -60,6 +60,7 @@ Don't pick an interesting technology just to make your work more fun, using code
1. **Managers of one** We want team members to be [a manager of one](https://signalvnoise.com/posts/1430-hire-managers-of-one) who doesn't need daily check-ins to achieve their goals.
1. **Responsibility over rigidity** When possible we give people the responsibility to make a decision and hold them accountable for that instead of imposing rules and approval processes.
1. **Accept mistakes** Not every problem should lead to a new process to prevent them. Additional process make all actions more inefficient, a mistake only affects one.
1. **Move fast by shipping the minimum viable change** We value constant improvement by iterating quickly, month after month. If a task is too big to deliver in one month, cut scope.
 
## Diversity
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment