yaml 来自浏览器组件的EC2 SSH。

EC2 SSH from browser component.
---
AWSTemplateFormatVersion: "2010-09-09"
Description: EC2 SSH from browser component.

Parameters: 

  AppName: 
    Type: String
    Default: ec2-ssh-from-browser
    Description: Application Name.

  Vpc: 
    Type: String
    Default: ""
    Description: System VPC.

Resources:

  #############################################
  # SecurityGroup
  #############################################

  SG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: !Sub ${AppName}
      GroupDescription: EC2 SSH from browser Security Group
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 0
          ToPort: 65535
          CidrIp: 0.0.0.0/0
      VpcId: !Ref Vpc

  #############################################
  # Lambda
  #############################################

  LambdaRole: 
    Type: AWS::IAM::Role
    Properties: 
      RoleName: !Sub ${AppName}-lambda
      Path: /
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole

  LambdaPolicies: 
    Type: AWS::IAM::Policy
    Properties: 
      PolicyName: !Sub ${AppName}-lambda
      PolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - Effect: Allow
            Action:
              - ec2:AuthorizeSecurityGroupEgress
              - ec2:AuthorizeSecurityGroupIngress
              - ec2:DeleteSecurityGroup
              - ec2:RevokeSecurityGroupEgress
              - ec2:RevokeSecurityGroupIngress
              - ec2:DescribeSecurityGroups
              - ec2:DescribeSecurityGroupReferences
              - ec2:DescribeStaleSecurityGroups
            Resource: "*"
          - Effect: Allow
            Action:
              - autoscaling:Describe*
              - cloudwatch:*
              - logs:*
            Resource: "*"
      Roles:
        - !Ref LambdaRole

  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: !Sub ${AppName} SG change function.
      FunctionName: !Sub ${AppName}-sg-change
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaRole.Arn
      Runtime: python3.7
      Timeout: 300
      Environment:
        Variables:
          SGID1: !GetAtt SG.GroupId
      Code:
        ZipFile: !Sub
          |-
          # coding: UTF-8
          import os, json, urllib.request, boto3

          def lambda_handler(event, context):
              SSH_PORT = 22

              with urllib.request.urlopen(urllib.request.Request("https://ip-ranges.amazonaws.com/ip-ranges.json")) as res:
                  b = res.read()
                  j = json.loads(b)

              tl = [_["ip_prefix"] for _ in j["prefixes"] if _["service"] == "EC2_INSTANCE_CONNECT" and _["region"] == os.environ["AWS_DEFAULT_REGION"]]

              ec2, cl, rl, al, sgs = boto3.resource("ec2"), [], [], [], (os.environ["SGID1"], )
              for _ in sgs:
                  sg = ec2.SecurityGroup(_)
                  for __ in sg.ip_permissions:
                      for ___ in __["IpRanges"]:
                          ci = ___["CidrIp"]
                          if not ci in tl:
                              rl.append(ci)
                              sg.revoke_ingress(GroupName=sg.group_name, IpPermissions = [{ "IpProtocol": "tcp", "FromPort": SSH_PORT, "ToPort": SSH_PORT, "IpRanges": [___] }])
                          else:
                              cl.append(ci)

              al, c = list(set(tl) - set(cl)), 0
              for _ in al:
                  while c < len(sgs):
                      sg = ec2.SecurityGroup(sgs[c])
                      if len(sg.ip_permissions) == 0 or len(sg.ip_permissions[0]["IpRanges"]) < 50:
                          break
                      c += 1
                      del sg # Run del to raise reference error in sg.authorize_ingress

                  sg.authorize_ingress(GroupName=sg.group_name, IpPermissions = [{ "IpProtocol": "tcp", "FromPort": SSH_PORT, "ToPort": SSH_PORT, "IpRanges": [{"CidrIp": _}] }])

              print("EC2_INSTANCE_CONNECT CidrIps Total [%s] Add [%s] Remove [%s]" % (len(tl), len(al), len(rl)))
              print("EC2_INSTANCE_CONNECT CidrIps --------------------")
              print(tl)
              print("Add CidrIps --------------------------")
              print(al)
              print("Remove CidrIps -----------------------")
              print(rl)
              print("--------------------------------------")

              return "Success"

  LambdaEntryScheduledRule:
    Type: "AWS::Events::Rule"
    Properties:
      Name: !Sub ${AppName}-sg-change-cron
      Description: !Sub ${AppName} ScheduledRule
      ScheduleExpression: cron(0 16 * * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt Lambda.Arn
          Id: !Ref Lambda

  PermissionForEventsToInvokeLambda:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt Lambda.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt LambdaEntryScheduledRule.Arn

yaml liferay码头组成

docker-compose.yml
version: '3'
services:
  liferay:
    image: liferay/portal:7.1.3-ga4
    ports:
      - "8080:8080"
    depends_on:
      - mysql
    volumes:
      - ./docker/local:/etc/liferay/mount
  mysql:
    image: mysql:5.7.26
    ports:
      - "3307:3306"
    environment:
      MYSQL_DATABASE: 'lportal'
      MYSQL_ROOT_PASSWORD: 'root'
    command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci']

yaml 头盔包

头盔包

azure-pipelines.yml
- task: HelmDeploy@0
  displayName: Helm package
  inputs:
    command: package
    chartPath: $(chartPath)
    destination: $(Build.ArtifactStagingDirectory)
    version: $(Build.BuildNumber)
    updatedependency: true
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'charts'
    publishLocation: 'Container'

yaml Spring Boot服务部署

Spring Boot服务部署

application.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: {{ template "spring-demo.fullname" . }}-deployment
spec:
    replicas: {{ .Values.scale }}
    selector:
        matchLabels:
            app: {{ template "spring-demo.fullname" . }}-spring
    template:
        metadata:
            labels:
                app: {{ template "spring-demo.fullname" . }}-spring
        spec:
            containers:
            - name: {{ template "spring-demo.fullname" . }}-container
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"         
              envFrom:
                - configMapRef:
                    name: {{ template "spring-demo.fullname" . }}-env
              env:
                - name: MYSQL_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: {{ .Release.Name }}-mysql
                      key: mysql-password    
                - name: JWT_SECRET
                  valueFrom:
                    secretKeyRef:
                      name: {{ template "spring-demo.fullname" . }}-jwt-secret
                      key: jwt-secret              
              ports:
                - containerPort: 8080
            imagePullSecrets:
            - name: {{ template "spring-demo.fullname" . }}-acr-secret

yaml Spring Boot服务部署

Spring Boot服务部署

application.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: {{ template "spring-demo.fullname" . }}-deployment
spec:
    replicas: {{ .Values.scale }}
    selector:
        matchLabels:
            app: {{ template "spring-demo.fullname" . }}-spring
    template:
        metadata:
            labels:
                app: {{ template "spring-demo.fullname" . }}-spring
        spec:
            containers:
            - name: {{ template "spring-demo.fullname" . }}-container
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"         
              envFrom:
                - configMapRef:
                    name: {{ template "spring-demo.fullname" . }}-env
              env:
                - name: MYSQL_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: {{ .Release.Name }}-mysql
                      key: mysql-password    
                - name: JWT_SECRET
                  valueFrom:
                    secretKeyRef:
                      name: {{ template "spring-demo.fullname" . }}-jwt-secret
                      key: jwt-secret              
              ports:
                - containerPort: 8080
            imagePullSecrets:
            - name: {{ template "spring-demo.fullname" . }}-acr-secret

yaml Azure管道

Azure管道

azure-pipelines.yml
pool:
  vmImage: 'ubuntu-latest'

variables:
  imageName: spring-demo


steps:
- task: Maven@3
  inputs:
    mavenPomFile: 'pom.xml'
    options: '-Dspring.profiles.active=dev'
    mavenOptions: '-Xmx3072m'
    javaHomeOption: 'JDKVersion'
    jdkVersionOption: '1.8'
    jdkArchitectureOption: 'x64'
    publishJUnitResults: false
    testResultsFiles: '**/surefire-reports/TEST-*.xml'
    goals: 'package'
- script: |
    echo Build docker image
    docker build -t $username.azurecr.io/$(imageName):$(Build.BuildNumber) .
    echo Login to docker repository
    docker login -u $username -p $password $username.azurecr.io
    echo Push docker image
    docker push $username.azurecr.io/$(imageName):$(Build.BuildNumber)
  displayName: 'Build and Push Docker Image'
  env:
    username: $(acrUsername)
    password: $(acrPassword)

yaml Jekyll Config

Jekyll Config

_config.yml
name: Oğuzhan Kırçalı
title: Blog | Oğuzhan Kırçalı
description: Yazılım Geliştiricisi
meta_description: Oğuzhan Kırçalı | Yazılım Geliştiricisi
logo: assets/images/logo.png
favicon: assets/images/favicon.ico
paginate: 10
baseurl: /blog
email: oguzhankircali@gmail.com
google_analytics: UA-15831320
disqus: https-oguzhankircali-github-io-blog
sharethis: >-
  <script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=5d00e7df4351e900126504cf&product=social-ab' async='async'></script>
authors:
  oguzhankircali:
    name: Oğuzhan Kırçalı
    display_name: Oğuzhan Kırçalı
    gravatar: assets/images/foolish-logo.png
    email: oguzhankircali@gmail.com
    web: https://oguzhankircali.github.io/blog
    twitter: 'https://twitter.com/oguzhankircali'
    description: >-
      C#, .Net, .Net Core, Web API, Windows/Web Servisleri, AngularJS, SignalR... Ben!
plugins:
  - jekyll-paginate
  - jekyll-sitemap
  - jekyll-feed
  - jekyll-seo-tag
  - jekyll-archives
  - jekyll-admin
  - jekyll-gist
jekyll-archives:
  enabled:
    - categories
  layout: archive
  permalinks:
    category: '/blog/category/:name/'
markdown: kramdown
highlighter: rouge
comments: true
sitemap: true
collections:
  staff_members:
    people: true
exclude: [node_modules]

yaml Prometheus规则重新绑定实例:port to te只是实例IP

prometheus_relabel_instance
 relabel_configs:
    - source_labels: [__address__]
      regex: "([^:]+):\\d+"
      target_label: instance

yaml 适用于api平台的DataProvider

NetworkCollectionDataProvider
<?php

namespace App\DataProvider;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use ApiPlatform\Core\Exception\RuntimeException;
use App\Entity\Company;
use Doctrine\Common\Persistence\ManagerRegistry;

class NetworkCollectionDataProvider implements CollectionDataProviderInterface
{
    private $managerRegistry;
    private $collectionExtensions;

    /**
     * @param ManagerRegistry                     $managerRegistry
     * @param QueryCollectionExtensionInterface[] $collectionExtensions
     */
    public function __construct(ManagerRegistry $managerRegistry, array $collectionExtensions = [])
    {
        $this->managerRegistry = $managerRegistry;
        $this->collectionExtensions = $collectionExtensions;
    }

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return Company::class === $resourceClass;
    }

    /**
     * {@inheritdoc}
     *
     * @throws RuntimeException
     */
    public function getCollection(string $resourceClass, string $operationName = null): Paginator
    {
        $manager = $this->managerRegistry->getManagerForClass($resourceClass);
        if (null === $manager) {
            throw new ResourceClassNotSupportedException();
        }
        $repository = $manager->getRepository($resourceClass);
        if (!method_exists($repository, 'createQueryBuilder')) {
            throw new RuntimeException('The repository class must have a "createQueryBuilder" method.');
        }
        $queryBuilder = $repository->findNetworkArray();
        $queryNameGenerator = new QueryNameGenerator();
        foreach ($this->collectionExtensions as $extension) {
            $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName);
            if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) {
                return $extension->getResult($queryBuilder);
            }
        }
        return $queryBuilder->getQuery()->getResult();
    }
}
CompanyController
<?php

namespace App\Controller;

use App\Entity\Company;
use App\Repository\CompanyRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
use Symfony\Component\HttpFoundation\Request;
use App\DataProvider\NetworkCollectionDataProvider;

class CompanyController extends AbstractController
{

    private $networkCollectionDataProvider;

    public function __construct(NetworkCollectionDataProvider $networkCollectionDataProvider)
    {
        $this->networkCollectionDataProvider = $networkCollectionDataProvider;
    }

    /**
     * @Route(
     *     name="network",
     *     path="v1/network",
     *     methods={"GET"},
     *     defaults={
     *       "_api_resource_class"="App\Entity\Company",
     *       "_api_collection_operation_name"="network"
     *     }
     *   )
     */
    public function __invoke()
    {
        return  $this->networkCollectionDataProvider->getCollection(Company::class);
    }
}
services
App\DataProvider\NetworkCollectionDataProvider:
        tags: [ { name: 'api_platform.collection_data_provider', priority: 2 } ]
        # Autoconfiguration must be disabled to set a custom priority
        autoconfigure: false

yaml 挑战:在Kuberntes上创建一个简约的环境来运行一个小型Web应用程序

Dockerfile
FROM debian:stretch-slim

LABEL version="base" maintainer="ales.loncar@celavi.org"

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
    ca-certificates \
    libpq-dev \
    dumb-init \
    perl \
    cpanminus \
    build-essential \
    procps \
    && cpanm Carton \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

COPY . /opt/app
WORKDIR /opt/app

RUN carton install --deployment

RUN rm -rf /root/.cpanm \
    && apt-get remove --purge -y build-essential \
    && apt-get autoremove -y \
    && rm -rf /tmp/* \
    && rm -rf /var/tmp/*

RUN groupadd -r app \
   && useradd -r -g app app \
   && chown app:app -R /opt/app

USER app

# Expose the default web server port
EXPOSE 8080

# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

# Run this command when container is started
CMD carton exec hypnotoad -f script/blog
docker-compose.yml
version: '2'

services:
  blog:
    image: celavi/mojo-blog:latest
    restart: always
    ports:
      - '8080'
    volumes:
      - ./etc/blog.conf:/opt/app/etc/blog.conf
    depends_on:
      - db

  db:
    image: postgres:9.6.11
    restart: always
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: s3cret
README.md
# Mojo Blog

Mojo Blog is simple example with [DBD::Pg](https://metacpan.org/pod/DBD::Pg) that makes [PostgreSQL](https://www.postgresql.org) a lot of fun to use with the [Mojolicious](https://mojolicious.org) real-time web framework.

## Local installation

### Perl

You can use system Perl version for development. I recommend the use of `perlbrew` or `plenv` environment.

- [perlbrew](https://perlbrew.pl/)
- [plenv](https://github.com/tokuhirom/plenv/)

### Prerequisites

Cpanminus and Carton are the only prerequisite for running the application. All required modules/dependencies are then installed via Carton from cpanfile.

- [App::cpanminus - get, unpack, build and install modules from CPAN](https://github.com/miyagawa/cpanminus/tree/devel/App-cpanminus)
- [Carton - Perl module dependency manager (aka Bundler for Perl)](https://metacpan.org/pod/Carton)

### Configuration

For Perl-ish configuration Mojolicious plugin is used - [Mojolicious::Plugin::Config](http://mojolicious.org/perldoc/Mojolicious/Plugin/Config).

## Building the Docker container image

### Building the latest image

```bash
$ docker build -t <NAME:TAG> .
```

### Running tests inside container image

```bash
$ docker run --rm -v $PWD/t:/opt/app/t <IMAGE> carton exec prove -lr -j4
```

## Working with Helm

### Install `tiller` in the Minikube cluster

```bash
$ helm init
```

### We can do a dry-run of a helm install and enable debug to inspect the generated definitions:

```bash
$ helm install --dry-run --debug --namespace=default ./helm
```

### Install

```bash
$ helm install --name mojo-blog --namespace=default ./helm
```

### Check the status of the release

```bash
$ helm ls --all mojo-blog
```

### Delete the release

```bash
$ helm del --purge mojo-blog
```

### Do the linting

```bash
$ helm lint ./helm
```

### Render chart templates locally and store them as Kubernetes YAML

```bash
$ helm template --name mojo-blog --namespace=default ./helm  > mojo-blog.yaml
```

## Use Port Forwarding to Access Applications in a Cluster

### Forward a local port to a port on the pod / deployment / service

```bash
$ kubectl port-forward <POD> 8080:8080
# or
$ kubectl port-forward dc/<DEPLOYMENT> 8080:8080
# or
$  kubectl port-forward svc/<SERVICE> 8080:8080
```
ANALYSIS.md
# Analysis

## Handling sensitive data

Currently sensitive information, such as passwords, server IP addresses, ... are hard-coded and stored in plain text. If sensitive data must be saved, it must be encrypted first. The best option would be to use a tool for securely managing secrets and encrypting, for example, HashiCorp Vault.

## Automation

Artefact building and method of deployment are done manually as described in [README.md](README.md) file. The whole process (configuration, software provisioning and application deployment) can be fully automated. To solve the problem of environment drifting in release pipeline Infrastructure as Code (IaC) approach should be used. Continuous configuration automation (CCA) tools can be thought of as an extension of traditional IaC frameworks. Notable CCA tools: Ansible, SaltStack, Terraform.

## Database

For simplicity, PostgreSQL database service is currently deployed without persistent storage (Ephemeral) and, therefore, is not production ready. Any data stored will be lost upon pod destruction. Data persistence on Kubernetes is achieved with Persistent Volumes which provide a plugin model for storage in Kubernetes where how storage is provided is completed abstracted from how it is consumed. For production, PostgreSQL replicated database service with persistent storage (highly available with automated failover and backup) should be used.

## Pipeline

Current Gitlab CI/CD and Jenkins pipeline streamline the very simplified process of build, tag and release for only one environment - TEST. The production TEST stages should include

- Test and Analysis (running unit tests and code quality analysis for using SonarQube or equivalent)
- Deploy (As soon as the latest Docker image is built and pushed to Docker registry a new deployment based on this most recent image is rolled out on the Test environment. This stage will wait until the deployment is fully rolled out and ready.)
- (Optional) Smoke Tests and Module Integration Test (For some implementations it might be useful to implement smoke test and module integration tests. These tests can be executed after the successful rollout of a new deployment.)
- Tag Docker Image (After the successful rollout (and optional smoke and module integration tests), the image is tagged with the current build version.)
- Create GIT Release Tag (Finally, the current GIT commit is tagged with the current image build version.)

The next steps can include transporting immutable images across the various stages (INT, PROD) or even across the different clusters.

## Software Versioning

Versioning is essential in application development. It must be possible to relate every deployment to one unique SCM commit to:

- Verify that the correct version with all its desired features is deployed
- Reproduce errors or problems by analysing the code of the exact SCM commit
- Rolling out one specific (stable) version of the application to other environments

To keep it clear and straightforward semantic versioning is used which is also used for versioning the Libraries in software development. For Continuous microservices release cycles, on the other hand, rely heavily on these time-based parameters due to their much shorter lifetime and could almost renounce semantic versioning entirely. Though it is recommended to keep a semantic version for microservices to indicate major and minor releases and track microservice development from a top view.

Example: 20170717.111749-master-1.2.3

Additional tagging should also be applied after successful rollouts to INT / PROD in GIT.

## Hypnotoad Prefork Web Server

Mojo::Server::Hypnotoad is a built-in prefork web server in production environment. hypnotoad is command line interface to run Mojolicious application. Server start on port 8080 by default which is also port exposed in Docker image. In production generally reverse proxy server (Nginx, Apache) is used to access hypnotoad server.
Jenkinsfile
pipeline {
    agent any
    options { disableConcurrentBuilds() }
    environment {
        registry = "celavi/mojo-blog"
        registryCredential = 'dockerhub'
    }
    stages {
        stage("SCM Checkout") {
            steps{
                git poll: false, url: 'https://github.com/loncarales/mojo-pg.git'
            }
        }

        stage('Config') {
            steps {
                script {
                    dir('examples/blog') {
                        version = readFile('VERSION').trim()
                    }
                }
            }
        }

        stage("Build") {
            steps {
                script {
                    dir('examples/blog') {
                        dockerImage = docker.build registry + ":build"
                        docker.withRegistry('', registryCredential ) {
                            dockerImage.push()
                        }
                    }
                }
            }
        }

        stage("Test") {
            steps {
                script {
                    dir('examples/blog') {
                        dockerImage.pull()
                        sh 'docker run --rm -v $PWD/t:/opt/app/t $registry:build carton exec prove -lr -j4'
                    }
                }
            }
        }

        stage("Release") {
            steps {
                script {
                    dir('examples/blog') {
                        dockerImage.pull()
                        dockerImage.tag('v' + version)
                        dockerImage.tag('latest')
                        docker.withRegistry('', registryCredential ) {
                            dockerImage.push('v' + version)
                            dockerImage.push('latest')
                        }
                    }
                }
            }
        }

        stage("Deploy") {
            steps {
                script {
                    dir('examples/blog') {
                        echo "TBA"
                    }
                }
            }
        }
    }
}
gitlab-ci.yml
image: docker:stable

services:
  - docker:dind

variables:
  CONTAINER_REGISTRY: "registry.gitlab.com"
  DOCKER_IMAGE: "loncarales/mojo-blog"

before_script:
  - 'docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CONTAINER_REGISTRY'

stages:
  - build
  - test
  - release
  - deploy

build:
  stage: build
  tags:
    - docker
  script:
    - 'docker build -t $CONTAINER_REGISTRY/$DOCKER_IMAGE:build .'
    - 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
  only:
    - master

test:
  stage: test
  tags:
    - docker
  script:
    - 'docker pull $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
    - 'docker run --rm -v $PWD/t:/opt/app/t $CONTAINER_REGISTRY/$DOCKER_IMAGE:build carton exec prove -lr -j4'
  only:
    - master

release:
  stage: release
  tags:
    - docker
  script:
    - 'docker pull $CONTAINER_REGISTRY/$DOCKER_IMAGE:build'
    - 'VERSION=$(cat VERSION)'
    - 'docker tag $CONTAINER_REGISTRY/$DOCKER_IMAGE:build $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION'
    - 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION'
    - 'docker tag $CONTAINER_REGISTRY/$DOCKER_IMAGE:v$VERSION $CONTAINER_REGISTRY/$DOCKER_IMAGE:latest'
    - 'docker push $CONTAINER_REGISTRY/$DOCKER_IMAGE:latest'
  only:
    - master

deploy:
  stage: deploy
  tags:
    - docker
  script:
    - 'echo TBA'
  only:
    - master