kontena-mortar 0.1.0 → 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adf60fa80da057855968f430ee78c54ed1213b8db335512c38256b51644a1a27
4
- data.tar.gz: cadede47cf01a39158a8b0f1b25be097d40c7a7b1b4d03626bb5ee33e1e9cc99
3
+ metadata.gz: bea90dd703d70062d7fdc1d1fb6df91c4d58db52caa8e2e106734a82b713cb8b
4
+ data.tar.gz: 50ff8a0d860da001ad116b1037424aad567dbd01f82499fc2f81c0418fcf29c7
5
5
  SHA512:
6
- metadata.gz: 938e4e8013e6e04205144d58f972b401c60798129db76d4f657f33f9384601295cd807c1d7f24e505631a373ff5aaa31813281da0171ba419d2b1ca44c01901e
7
- data.tar.gz: 1f86940abc8e582c6c2b3feb94583760cf1cf8bf9f694f60eed86ec65153a21e7f82cd12c820b6882b0c70157daad9115b70444d6ea5433f90a173bfc1839667
6
+ metadata.gz: c0faca7f612c4924b7dc175d3faf0c7bb11a7dbf018473043a60c6a4152f3ffa0be598fc1606cb7d5188b8f34b9f0102c709797422ddb677bd421f853d838c8f
7
+ data.tar.gz: 3a7dedfd64a08550a725e12cbc27fcdee3ac15bc3658312b2aca604434754acf46c082fafb395dad599c68856f9ecab439d433496cde925c65074ebfb48bec56
data/.drone.yml CHANGED
@@ -3,8 +3,7 @@ pipeline:
3
3
  image: ruby:2.4
4
4
  commands:
5
5
  - bundle install --path bundler
6
- # Uncomment when we have proper tests etc. in place
7
- # - bundle exec rspec spec/ && bundle exec rubocop --fail-level A -S --format c --parallel
6
+ - bundle exec rspec spec/ # && bundle exec rubocop --fail-level A -S --format c --parallel
8
7
  docker_latest:
9
8
  registry: quay.io
10
9
  image: plugins/docker
@@ -35,3 +34,20 @@ pipeline:
35
34
  - gem push *.gem
36
35
  when:
37
36
  event: ['tag']
37
+
38
+ create_gh_release:
39
+ image: ubuntu:xenial
40
+ secrets: [ github_token ]
41
+ commands:
42
+ - ./build/drone/create_release.sh
43
+ when:
44
+ event: tag
45
+ build_xenial:
46
+ image: ubuntu:xenial
47
+ secrets: [ github_token ]
48
+ environment:
49
+ - CPPFLAGS=-P
50
+ commands:
51
+ - ./build/drone/ubuntu_xenial.sh
52
+ when:
53
+ event: tag
data/.travis.yml ADDED
@@ -0,0 +1,28 @@
1
+ language: ruby
2
+ sudo: required
3
+ cache: bundler
4
+ bundler_args: --without development
5
+ env:
6
+ global:
7
+ - secure: "AX0y/vETBJ4C93/RWgTZNFwlm8r+4b0cWExOa3G4NBvxc3x1PBjA9sD7LOezUxMCaOuTM1GF+vpXupreeZtdU6mUqQNJSsOH5sWivPfQwCmxbhODW/CGhlhKZT0pTJQ2QhUbp6zXeLfQ3WJT4X4VH2NP5zkfJ7Q0hO7AJqqty/+mAZLbtaBkw1eKhwWPx1t3UQx9zY07V2WsJQCmj9O/bdMyoa3uoTvSahzvAQsY/1XQ6OMZrVvt7JGoFjEVLNnttT3at3HvG3Izwb8u+ftrCr+0u6V9tUPAH+1WHJgP1fYrysU2n+HwhvGYlkl3TYt0wxP9ccz18NiFGf3SOf+OSBQJ11cQ6RJ7R6Qo/RLpW2AffjYW881d32pORbVsEpj8vBvElfJKbl0rT4IgIRJxxCuZnqVbX+8szma9EAmltGbtNdyuX4WxARZ3XNlFONacX5mnubRZDruYijNPzv3tCb6KBaRV5yfg8dhMPjOf6r0i8r0+9IFRsfszVMSxJhrnXlhtaYn7OikqBDOSiCa/VeSBb09fl/klnTCpCHFRICYgWqO8vf1jWvXLuKtHA7E3Ah4CJTkirY4eFwLMEN2kJ6g28OudWMU/urL4/rRb4LpzlDySy9KFA8f1/+7A/mzLDv7Iket2ig6/Pa7ymnYSlmypzVZTvcpJr0EfK+hmr6E="
8
+ stages:
9
+ - name: publish binary
10
+ if: tag IS present
11
+ jobs:
12
+ include:
13
+ - stage: publish binary
14
+ script: ./build/travis/macos.sh
15
+ rvm: 2.4
16
+ os: osx
17
+ deploy:
18
+ provider: s3
19
+ access_key_id:
20
+ secure: "OHjLg4bzJBUdwmw42XK5s/+5tHUy7SO32/NzV5qCETIw+lr0lEhHIUeGUDGEqsMKvB3X1rIyFhhYJcRUCkJHfDUM2uRyIxYl/HUNNHCnZttwBHsn517Wt8sSzx5mSHuqXl54hoI47vvx0yfKrvW70/AF0aNufEde5tQePzsgghZ0FIPKx29CiG0QAufTgJ3B3tXJtDywDXO6kyQmmpjSTO+Rne8YEx4MvyoGH1DhHiiSsggSMrYUy80mZP967AvQ6cCQNS7d9A1ThNjJVNGQ9jzTGW8vWRsvI1Y5583sWwAwVbKSYtcaO2t5IC1q62PnYu9xVyy6D5QZ4uJ4jQk8at00348nj059CosNEE9IdqRaTJi5TIiX9H1nU8y3P/c/2dYpsQIKch0Ji/cQhl4RCe+QQuOpCzggElf8GeD/tg9GJFhX+uPPWFJMl4zvrH5EascRm+PHJsr7UFL0Lv4Q0x85qdCa/0Oh1XA+f+WaLpSkUUiLUUB27+2dLupD3VyuSZ0IferiDHHgIG0teXoSvI6hgSBg85ZYZKls72seTuGG0icrn+U/iz+7ywtaaS5yVmrelUiYq4ElZr3N9SPaWUxUGXo734B60AIJ9dPQQJY8e+TJ2jjX3OCAd//Z3Kh2+O+nbcHUUMY4OfUVp4wcRy2LKlNqPC5DevZij2uVmKM="
21
+ secret_access_key:
22
+ secure: "HCnQTlTJ3jo4oX2ZLXZUUWHOL7yiV/z2dVNTmiQYGJ8Xsf34zLnwRvPTymq4nA7elCmO0J6G2ub1wqBLZqyYHhbhI//9txPpRJmgFf7C3R0c9j5HRG2lxRJ8PNHuOzT5e2Zx1Aq9Aawy2+zXTuNMnTF0gtXg2tO3oHzs2h99mk5qeoLDAbu1HUd+W7ymalbhw+GH3D6X4mHuv/AUowOJuieIMpg/FPvu0/SXh4/KpSV54hlfSDYIqaBlMDxdAJsoJi/tKBegNMkLeXyaFf9PYF6U83P8RABdBkhinatN0b6EJB21mmaRkfVam4gNhEgB+SobZVelmLLAsTZU9kEkt2V4zBgUYUbNI32ZXMhMFsOZtPfInbGAzqZPmKGRh39yFnN92uQjxWJXocGFrk43K3d2lviAntP05+xPpBjO4CDXuC8mqyRZf6vg/Cysu3OuxrUGMru0EHePG6DZDcH8yNq7GcBbarA5+w+hupnfHD8SoRtnSuyPdyPsjGb25vikkgeLN8J8d2FxcUH9NBgeBJXhMcXCKCxmIgCcMtJ1NElip6MktTYMl2nkxDmgpPljUG0QL95fVq88jwWhyxTvpASJvUSDbq0AUWNP5xJ4R+4vlWkSkVrtLhn4IaJ4ou6PATmgzyaw+0EG4JeAT2TfP8z6ILv5NNb/4YThwg08fVA="
23
+ bucket: "pharos-cluster-binaries"
24
+ region: "eu-west-1"
25
+ local-dir: upload
26
+ skip_cleanup: true
27
+ on:
28
+ all_branches: true
data/Dockerfile CHANGED
@@ -2,14 +2,12 @@ FROM ruby:2.5-alpine as build
2
2
 
3
3
  ADD . /src
4
4
 
5
- RUN apk --update add git && \
6
- cd /src ; gem build kontena-mortar.gemspec
5
+ RUN apk --update add git build-base && \
6
+ cd /src ; gem build kontena-mortar.gemspec && \
7
+ gem install *.gem
7
8
 
8
9
  FROM ruby:2.5-alpine
9
10
 
10
- COPY --from=build /src/*.gem /tmp/
11
-
12
- RUN gem install /tmp/*.gem && \
13
- rm -f /tmp/*.gem
11
+ COPY --from=build /usr/local/bundle /usr/local/bundle
14
12
 
15
13
  ENTRYPOINT [ "/usr/local/bundle/bin/mortar" ]
data/Gemfile.lock CHANGED
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kontena-mortar (0.1.0)
4
+ kontena-mortar (0.2.0.pre1)
5
5
  clamp (~> 1.3)
6
6
  deep_merge (~> 1.2)
7
- k8s-client (~> 0.3)
7
+ k8s-client (~> 0.4.1)
8
+ pastel (~> 0.7.2)
8
9
  rouge (~> 3.2)
9
10
 
10
11
  GEM
@@ -39,13 +40,19 @@ GEM
39
40
  dry-equalizer (~> 0.2)
40
41
  dry-inflector (~> 0.1, >= 0.1.2)
41
42
  dry-logic (~> 0.4, >= 0.4.2)
43
+ equatable (0.5.0)
42
44
  excon (0.62.0)
45
+ hashdiff (0.3.7)
43
46
  ice_nine (0.11.2)
44
- k8s-client (0.3.4)
47
+ k8s-client (0.4.1)
45
48
  deep_merge (~> 1.2.1)
46
49
  dry-struct (~> 0.5.0)
47
50
  excon (~> 0.62.0)
51
+ hashdiff (~> 0.3.7)
48
52
  recursive-open-struct (~> 1.1.0)
53
+ pastel (0.7.2)
54
+ equatable (~> 0.5.0)
55
+ tty-color (~> 0.4.0)
49
56
  rake (10.5.0)
50
57
  recursive-open-struct (1.1.0)
51
58
  rouge (3.2.1)
@@ -62,6 +69,7 @@ GEM
62
69
  diff-lcs (>= 1.2.0, < 2.0)
63
70
  rspec-support (~> 3.8.0)
64
71
  rspec-support (3.8.0)
72
+ tty-color (0.4.3)
65
73
 
66
74
  PLATFORMS
67
75
  ruby
data/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Kontena Mortar
2
2
 
3
- Kubernetes manifest shooter.
3
+ ![Mortar - Manifest shooter for Kubernetes](kontena-mortar.png)
4
+
5
+ Mortar is a tool to easily handle a complex set of Kubernetes resources. Using `kubectl apply -f some_folder/` is pretty straightforward for simple cases, but often, especially in CI/CD pipelines things get complex. Then on the otherhand, writing everything in Helm charts is way too complex.
6
+
7
+ While we were developing [Kontena Pharos](https://kontena.io/pharos) Kubernetes distro and tooling around it, we soon realized that we really want to manage sets of resources as a single unit. This thinking got even stronger while we were transitioning many of our production solutions to run on top of Kubernetes. As this is a common problem for all Kubernetes users, Mortar was born.
8
+
9
+ ## Features
10
+
11
+ - [Management of sets of resources as a single unit](#shots)
12
+ - [Simple templating](#templating)
13
+ - [Overlays](#overlays)
4
14
 
5
15
  ## Installation
6
16
 
@@ -12,6 +22,8 @@ Docker:
12
22
 
13
23
  `$ docker pull quay.io/kontena/mortar:latest`
14
24
 
25
+ We will be working on also to support binary installation method in near future.
26
+
15
27
  ## Usage
16
28
 
17
29
  ### Configuration
@@ -21,13 +33,19 @@ By default mortar looks for if file `~/.kube/config` exists and uses it as the c
21
33
  For CI/CD use mortar also understands following environment variables:
22
34
 
23
35
  - `KUBE_SERVER`: kubernetes api server address, for example `https://10.10.10.10:6443`
24
- - `KUBE_TOKEN`: service account token
36
+ - `KUBE_TOKEN`: service account token (base64 encoded)
25
37
  - `KUBE_CA`: kubernetes CA certificate (base64 encoded)
26
38
 
27
39
  ### Deploying k8s yaml manifests
28
40
 
29
41
  ```
30
- $ mortar <deployment-name> <src-folder>
42
+ $ mortar fire [options] <src-folder> <deployment-name>
43
+ ```
44
+
45
+ ### Removing a deployment
46
+
47
+ ```
48
+ $ mortar yank [options] <deployment-name>
31
49
  ```
32
50
 
33
51
  ### Docker image
@@ -46,6 +64,96 @@ pipeline:
46
64
 
47
65
  ```
48
66
 
67
+ ## Shots
68
+
69
+ Mortar manages a set of resources as a single unit, we call them *shots*. A shot can have as many resources as your application needs, there's no limit to that. Much like you'd do with `kubectl apply -f my_dir/`, but Mortar actually injects information into the resources it shoots into your Kubernetes cluster. This added information, labels and annotations, will be used later on by Mortar itself or can be used with `kubectl` too. This allows the management of many resources as a single application.
70
+
71
+ Most importantly, Mortar is able to use this information when re-shooting your applications. One of the most difficult parts when using plain `kubectl apply ...` approach is the fact that it's super easy to leave behind some lingering resources. Say you have some `deployments` and a `service` in your application, each defined in their own `yaml` file. Now you remove the service and re-apply with `kubectl apply -f my_resources/`. The service will live on in your cluster. With Mortar, you don't have to worry. With the extra labels and annotations Mortar injects into the resources, it's also able to automatically prune the "un-necessary" resources from the cluster. The automatic pruning is done with `--prune` option.
72
+
73
+ See basic example [here](/examples/basic).
74
+
75
+ ## Overlays
76
+
77
+ One of the most powerful features of Mortar is it's ability to support *overlays*. An overlay is a variant of the set of resources you are managing. A variant might be for example the same application running on many different environments like production, test, QA an so on. A variant might also be a separate application "instance" for each customer. Or what ever the need is. Overlays in Mortar are inspired by [kustomize](https://github.com/kubernetes-sigs/kustomize).
78
+
79
+ Given a folder & file structure like:
80
+ ```
81
+ echoservice-metallb/
82
+ ├── echo-metal.yml
83
+ ├── foo
84
+ │   └── pod.yml.erb
85
+ └── prod
86
+ ├── pod.yml
87
+ └── svc.yml
88
+ ```
89
+
90
+ where overlays (`prod` & `foo`) contain same resources as the base folder, mortar now merges all resources together. Merging is done in the order overlays are given in the command. Resources are considered to be the same resource if all of these match: `kind`, `apiVersion`, `metadata.name` and `metadata.namespace`.
91
+
92
+ If there are new resources in the overlay dirs, they are taken into the shot as-is.
93
+
94
+ You'd select overlays taken into processing with `--overlay option`.
95
+
96
+ **Note:** Overlays are taken in in the order defined, so make sure you define them in correct order.
97
+
98
+ The resources in the overlays do not have to be complete, it's enough that the "identifying" fields are the same.
99
+
100
+ See example of overlays [here](/examples/overlays).
101
+
102
+ ## Templating
103
+
104
+ Mortar also support templating for the resource definitons. The templating language used is [ERB](https://en.wikipedia.org/wiki/ERuby). It's pretty simple templating language but yet powerful enough for Kubernetes resource templating.
105
+
106
+ Mortar automatically processes the resource definition as a template if the filename is either `.yml.erb` or `.yaml.erb`.
107
+
108
+ There are two ways to introduce variables into the templating.
109
+
110
+ See examples at [examples/templates](examples/templates).
111
+
112
+ ### Environment variables
113
+
114
+ As for any process, environment variables are also available for Mortar during template processing.
115
+
116
+ ```
117
+ kind: Pod
118
+ apiVersion: v1
119
+ metadata:
120
+ name: nginx
121
+ labels:
122
+ name: nginx
123
+ namespace: default
124
+ spec:
125
+ containers:
126
+ - name: nginx
127
+ image: nginx:<%= ENV["NGINX_VERSION"] || "latest" %>
128
+ ports:
129
+ - containerPort: 80
130
+ ```
131
+
132
+ ### Variables via options
133
+
134
+ Another option to use variables is via command-line options. Use `mortar --var foo=bar my-app resources/`.
135
+
136
+ Each of the variables defined will be available in the template via `var.<variable name>`.
137
+
138
+ ```
139
+ kind: Pod
140
+ apiVersion: v1
141
+ metadata:
142
+ name: nginx
143
+ labels:
144
+ name: nginx
145
+ namespace: default
146
+ spec:
147
+ containers:
148
+ - name: nginx
149
+ image: nginx:latest
150
+ ports:
151
+ - containerPort: <%= port.number %>
152
+ name: <%= port.name %>
153
+ ```
154
+
155
+ You could shoot this resource with `mortar --var port.name=some-port --var port.number=80 my-app resources/pod.yml.erb`
156
+
49
157
 
50
158
  ## Contributing
51
159
 
@@ -59,4 +167,4 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
59
167
 
60
168
  http://www.apache.org/licenses/LICENSE-2.0
61
169
 
62
- Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
170
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
data/bin/mortar CHANGED
@@ -10,4 +10,4 @@ require 'mortar'
10
10
  $0 = 'mortar'
11
11
  $stdout.sync = true
12
12
 
13
- Mortar::Command.run
13
+ Mortar::RootCommand.run
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+
3
+ set -ue
4
+
5
+ apt-get update -y
6
+ apt-get install -y -q curl bzip2
7
+
8
+ # ship to github
9
+ curl -sL https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2 | tar -xjO > /usr/local/bin/github-release
10
+ chmod +x /usr/local/bin/github-release
11
+
12
+ if [[ $DRONE_TAG =~ .+-.+ ]]; then
13
+ /usr/local/bin/github-release release \
14
+ --user kontena \
15
+ --repo mortar \
16
+ --tag $DRONE_TAG \
17
+ --name $DRONE_TAG \
18
+ --description "Pre-release, only for testing" \
19
+ --draft \
20
+ --pre-release
21
+ else
22
+ /usr/local/bin/github-release release \
23
+ --user kontena \
24
+ --repo mortar \
25
+ --draft \
26
+ --tag $DRONE_TAG \
27
+ --name $DRONE_TAG
28
+ fi
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+
3
+ set -ue
4
+
5
+ # build binary
6
+ apt-get update -y
7
+ apt-get install -y -q squashfs-tools build-essential ruby bison ruby-dev git-core texinfo curl
8
+ curl -sL https://dl.bintray.com/kontena/ruby-packer/0.5.0-dev/rubyc-linux-amd64.gz | gunzip > /usr/local/bin/rubyc
9
+ chmod +x /usr/local/bin/rubyc
10
+ gem install bundler
11
+ version=${DRONE_TAG#"v"}
12
+ package="mortar-linux-amd64-${version}"
13
+ rubyc -o $package mortar
14
+ ./$package version
15
+
16
+ # ship to github
17
+ curl -sL https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2 | tar -xjO > /usr/local/bin/github-release
18
+ chmod +x /usr/local/bin/github-release
19
+ /usr/local/bin/github-release upload \
20
+ --user kontena \
21
+ --repo mortar \
22
+ --tag $DRONE_TAG \
23
+ --name $package \
24
+ --file ./$package
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+
3
+ set -ue
4
+
5
+ brew install squashfs
6
+ curl -sL https://dl.bintray.com/kontena/ruby-packer/0.5.0-dev/rubyc-darwin-amd64.gz | gunzip > /usr/local/bin/rubyc
7
+ chmod +x /usr/local/bin/rubyc
8
+ version=${TRAVIS_TAG#"v"}
9
+ package="mortar-darwin-amd64-${version}"
10
+ rubyc -o $package mortar
11
+ ./$package version
12
+
13
+ # ship to github
14
+ curl -sL https://github.com/aktau/github-release/releases/download/v0.7.2/darwin-amd64-github-release.tar.bz2 | tar -xjO > /usr/local/bin/github-release
15
+ chmod +x /usr/local/bin/github-release
16
+ /usr/local/bin/github-release upload \
17
+ --user kontena \
18
+ --repo mortar \
19
+ --tag $TRAVIS_TAG \
20
+ --name $package \
21
+ --file ./$package
22
+
23
+ mkdir -p upload
24
+ mv $package upload/
@@ -0,0 +1,18 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: myapp
5
+ spec:
6
+ selector:
7
+ matchLabels:
8
+ app: myapp
9
+ template:
10
+ metadata:
11
+ labels:
12
+ app: myapp
13
+ spec:
14
+ containers:
15
+ - name: myapp
16
+ image: nginx:latest
17
+ ports:
18
+ - containerPort: 80
@@ -0,0 +1,10 @@
1
+ kind: Service
2
+ apiVersion: v1
3
+ metadata:
4
+ name: myapp
5
+ spec:
6
+ selector:
7
+ app: myapp
8
+ ports:
9
+ - port: 80
10
+ targetPort: 80
@@ -0,0 +1,27 @@
1
+ # An dynamic annotation can be used to force Kubernetes to deploy pods.
2
+ # In this example mortar will set DRONE_BUILD_NUMBER environment variable as a value of 'build' annotation.
3
+ # Basically annotation can be, for example, datetime, commit sha or any unique value
4
+ apiVersion: apps/v1
5
+ kind: Deployment
6
+ metadata:
7
+ name: nginx
8
+ labels:
9
+ app: nginx
10
+ spec:
11
+ selector:
12
+ matchLabels:
13
+ app: nginx
14
+ strategy:
15
+ template:
16
+ metadata:
17
+ labels:
18
+ app: nginx
19
+ annotations:
20
+ build: "<%= ENV['DRONE_BUILD_NUMBER'] %>"
21
+ spec:
22
+ containers:
23
+ - image: docker.io/nginx:alpine
24
+ imagePullPolicy: Always
25
+ name: nginx
26
+ ports:
27
+ - containerPort: 80
@@ -0,0 +1,35 @@
1
+ ---
2
+ apiVersion: extensions/v1beta1
3
+ kind: Deployment
4
+ metadata:
5
+ name: echoserver
6
+ namespace: default
7
+ spec:
8
+ replicas: 1
9
+ template:
10
+ metadata:
11
+ labels:
12
+ app: echoserver
13
+ spec:
14
+ containers:
15
+ - image: gcr.io/google_containers/echoserver:1.0
16
+ imagePullPolicy: Always
17
+ name: echoserver
18
+ ports:
19
+ - containerPort: 8080
20
+ ---
21
+ apiVersion: v1
22
+ kind: Service
23
+ metadata:
24
+ name: echoserver
25
+ namespace: default
26
+ annotations:
27
+ metallb.universe.tf/address-pool: default
28
+ spec:
29
+ type: LoadBalancer
30
+ ports:
31
+ - port: 80
32
+ targetPort: 8080
33
+ protocol: TCP
34
+ selector:
35
+ app: echoserver
@@ -0,0 +1,14 @@
1
+ kind: Pod
2
+ apiVersion: v1
3
+ metadata:
4
+ name: myapp
5
+ labels:
6
+ name: myapp
7
+ foo: <%= var.foo.bar %>
8
+ spec:
9
+ containers:
10
+ - name: myapp
11
+ image: foobar
12
+ ports:
13
+ - containerPort: 8080
14
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ apiVersion: extensions/v1beta1
3
+ kind: Deployment
4
+ metadata:
5
+ name: echoserver
6
+ namespace: default
7
+ labels:
8
+ partial: works too :)
@@ -0,0 +1,13 @@
1
+ kind: Pod
2
+ apiVersion: v1
3
+ metadata:
4
+ name: myapp
5
+ labels:
6
+ name: myapp
7
+ spec:
8
+ containers:
9
+ - name: myapp
10
+ image: foobar
11
+ ports:
12
+ - containerPort: 8080
13
+
@@ -0,0 +1,14 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: echoserver
5
+ annotations:
6
+ environment: production
7
+ spec:
8
+ type: LoadBalancer
9
+ ports:
10
+ - port: 80
11
+ targetPort: 8080
12
+ protocol: TCP
13
+ selector:
14
+ app: echoserver
@@ -0,0 +1,13 @@
1
+ kind: Pod
2
+ apiVersion: v1
3
+ metadata:
4
+ name: nginx
5
+ labels:
6
+ name: nginx
7
+ namespace: default
8
+ spec:
9
+ containers:
10
+ - name: nginx
11
+ image: nginx:<%= ENV["NGINX_VERSION"] || "latest" %>
12
+ ports:
13
+ - containerPort: 80
@@ -0,0 +1,19 @@
1
+ kind: Pod
2
+ apiVersion: v1
3
+ metadata:
4
+ name: nginx
5
+ labels:
6
+ name: nginx
7
+ namespace: default
8
+ spec:
9
+ containers:
10
+ - name: nginx
11
+ image: nginx:latest
12
+ ports:
13
+ <%# Easy to loop through some variables with the dotted notation
14
+ # Run this with example mortar --var port.one=80 --var port.two=8080
15
+ %>
16
+ <% var.port.each do |name, number| %>
17
+ - containerPort: <%= number %>
18
+ name: <%= name %>
19
+ <% end %>
@@ -0,0 +1,15 @@
1
+ kind: Pod
2
+ apiVersion: v1
3
+ metadata:
4
+ name: nginx
5
+ labels:
6
+ name: nginx
7
+ namespace: default
8
+ spec:
9
+ containers:
10
+ - name: nginx
11
+ image: nginx:latest
12
+ ports:
13
+ <%# Variables can be accessed by the name easily %>
14
+ - containerPort: <%= port.number %>
15
+ name: <%= port.name %>
@@ -24,9 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ["lib"]
25
25
 
26
26
  spec.add_runtime_dependency "clamp", "~> 1.3"
27
- spec.add_runtime_dependency "k8s-client", "~> 0.3"
27
+ spec.add_runtime_dependency "k8s-client", "~> 0.4.1"
28
28
  spec.add_runtime_dependency "rouge", "~> 3.2"
29
29
  spec.add_runtime_dependency "deep_merge", "~> 1.2"
30
+ spec.add_runtime_dependency "pastel", "~> 0.7.2"
30
31
  spec.add_development_dependency "bundler", "~> 1.16"
31
32
  spec.add_development_dependency "rake", "~> 10.0"
32
33
  spec.add_development_dependency "rspec", "~> 3.0"
Binary file
@@ -0,0 +1,12 @@
1
+ module Extensions
2
+ module RecursiveOpenStruct
3
+ module Each
4
+ def each(&block)
5
+ self.to_h.each { |k,v| yield k.to_s,v }
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ # Monkey-patch the above module into RecursiveOpenStruct
12
+ RecursiveOpenStruct.include Extensions::RecursiveOpenStruct::Each
@@ -1,164 +1,10 @@
1
1
  require "clamp"
2
- require "base64"
3
- require_relative "yaml_file"
4
- require_relative "resource_helper"
5
-
6
- Clamp.allow_options_after_parameters = true
7
2
 
8
3
  module Mortar
9
4
  class Command < Clamp::Command
10
- include Mortar::ResourceHelper
11
-
12
- banner "mortar - Kubernetes manifest shooter"
13
-
14
- parameter "NAME", "deployment name"
15
- parameter "SRC", "source folder"
16
-
17
- option ["--var"], "VAR", "set template variables", multivalued: true
18
- option ["-d", "--debug"], :flag, "debug"
19
- option ["--output"], :flag, "only output generated yaml"
20
- option ["--prune"], :flag, "automatically delete removed resources"
21
- option ['-v', '--version'], :flag, "print mortar version" do
22
- puts "mortar #{Mortar::VERSION}"
23
- exit 0
24
- end
25
- option ["--overlay"], "OVERLAY", "overlay dirs", multivalued: true
26
-
27
5
  LABEL = 'mortar.kontena.io/shot'
28
6
  CHECKSUM_ANNOTATION = 'mortar.kontena.io/shot-checksum'
29
7
 
30
- def execute
31
- signal_usage_error("#{src} does not exist") unless File.exist?(src)
32
- resources = process_overlays
33
- if output?
34
- puts resources_output(resources)
35
- exit
36
- end
37
-
38
- K8s::Stack.new(
39
- name, resources,
40
- debug: debug?,
41
- label: LABEL,
42
- checksum_annotation: CHECKSUM_ANNOTATION
43
- ).apply(client, prune: prune?)
44
-
45
- puts "shot #{name} successfully!" if $stdout.tty?
46
- end
47
-
48
- def process_overlays
49
- resources = load_resources(src)
50
-
51
- overlay_list.each do |overlay|
52
- overlay_resources = from_files(overlay)
53
- overlay_resources.each do |overlay_resource|
54
- match = false
55
- resources = resources.map { |r|
56
- if same_resource?(r, overlay_resource)
57
- match = true
58
- r.merge(overlay_resource.to_hash)
59
- else
60
- r
61
- end
62
- }
63
- resources << overlay_resource unless match
64
- end
65
- end
66
-
67
- resources
68
- end
69
-
70
- # @param resources [Array<K8s::Resource>]
71
- # @return [String]
72
- def resources_output(resources)
73
- yaml = ''
74
- resources.each do |resource|
75
- yaml << ::YAML.dump(stringify_hash(resource.to_hash))
76
- end
77
- return yaml unless $stdout.tty?
78
-
79
- lexer = Rouge::Lexers::YAML.new
80
- rouge = Rouge::Formatters::Terminal256.new(Rouge::Themes::Github.new)
81
- rouge.format(lexer.lex(yaml))
82
- end
83
-
84
- # @return [RecursiveOpenStruct]
85
- def variables_struct
86
- return @variables_struct if @variables_struct
87
-
88
- set_hash = {}
89
- var_list.each do |var|
90
- k, v = var.split("=", 2)
91
- set_hash[k] = v
92
- end
93
- RecursiveOpenStruct.new(dotted_path_to_hash(set_hash))
94
- end
95
-
96
- def dotted_path_to_hash(hash)
97
- hash.map do |pkey, pvalue|
98
- pkey.to_s.split(".").reverse.inject(pvalue) do |value, key|
99
- {key.to_sym => value}
100
- end
101
- end.inject(&:deep_merge)
102
- end
103
-
104
- # @return [K8s::Client]
105
- def client
106
- return @client if @client
107
-
108
- if ENV['KUBE_TOKEN'] && ENV['KUBE_CA'] && ENV['KUBE_SERVER']
109
- @client = K8s::Client.new(K8s::Transport.config(build_kubeconfig_from_env))
110
- elsif ENV['KUBECONFIG']
111
- @client = K8s::Client.config(K8s::Config.load_file(ENV['KUBECONFIG']))
112
- elsif File.exist?(File.join(Dir.home, '.kube', 'config'))
113
- @client = K8s::Client.config(K8s::Config.load_file(File.join(Dir.home, '.kube', 'config')))
114
- else
115
- @client = K8s::Client.in_cluster_config
116
- end
117
- end
118
-
119
- # @return [K8s::Config]
120
- def build_kubeconfig_from_env
121
- token = ENV['KUBE_TOKEN']
122
- begin
123
- token = Base64.strict_decode64(token)
124
- rescue ArgumentError # raised if token is not base64 encoded
125
- end
126
- K8s::Config.new(
127
- clusters: [
128
- {
129
- name: 'kubernetes',
130
- cluster: {
131
- server: ENV['KUBE_SERVER'],
132
- certificate_authority_data: ENV['KUBE_CA']
133
- }
134
- }
135
- ],
136
- users: [
137
- {
138
- name: 'mortar',
139
- user: {
140
- token: token
141
- }
142
- }
143
- ],
144
- contexts: [
145
- {
146
- name: 'mortar',
147
- context: {
148
- cluster: 'kubernetes',
149
- user: 'mortar'
150
- }
151
- }
152
- ],
153
- preferences: {},
154
- current_context: 'mortar'
155
- )
156
- end
157
-
158
- # Stringifies all hash keys
159
- # @return [Hash]
160
- def stringify_hash(hash)
161
- JSON.load(JSON.dump(hash))
162
- end
8
+ option ["-d", "--debug"], :flag, "debug"
163
9
  end
164
10
  end
@@ -0,0 +1,108 @@
1
+ require "base64"
2
+ require_relative "command"
3
+ require_relative "yaml_file"
4
+ require_relative "mixins/resource_helper"
5
+ require_relative "mixins/client_helper"
6
+ require_relative "mixins/tty_helper"
7
+
8
+ module Mortar
9
+ class FireCommand < Mortar::Command
10
+ include Mortar::ResourceHelper
11
+ include Mortar::ClientHelper
12
+ include Mortar::TTYHelper
13
+
14
+ parameter "SRC", "source file or directory"
15
+ parameter "NAME", "deployment name"
16
+
17
+ option ["--var"], "VAR", "set template variables", multivalued: true
18
+ option ["--output"], :flag, "only output generated yaml"
19
+ option ["--prune"], :flag, "automatically delete removed resources"
20
+ option ["--overlay"], "OVERLAY", "overlay dirs", multivalued: true
21
+
22
+ def execute
23
+ signal_usage_error("#{src} does not exist") unless File.exist?(src)
24
+ resources = process_overlays
25
+
26
+ if output?
27
+ puts resources_output(resources)
28
+ exit
29
+ end
30
+
31
+ if resources.empty?
32
+ warn 'nothing to do!'
33
+ exit
34
+ end
35
+
36
+ K8s::Stack.new(
37
+ name, resources,
38
+ debug: debug?,
39
+ label: LABEL,
40
+ checksum_annotation: CHECKSUM_ANNOTATION
41
+ ).apply(client, prune: prune?)
42
+
43
+ puts "shot '#{pastel.cyan(name)}' successfully!" if $stdout.tty?
44
+ end
45
+
46
+ def process_overlays
47
+ resources = load_resources(src)
48
+
49
+ overlay_list.each do |overlay|
50
+ overlay_resources = from_files(overlay)
51
+ overlay_resources.each do |overlay_resource|
52
+ match = false
53
+ resources = resources.map { |r|
54
+ if same_resource?(r, overlay_resource)
55
+ match = true
56
+ r.merge(overlay_resource.to_hash)
57
+ else
58
+ r
59
+ end
60
+ }
61
+ resources << overlay_resource unless match
62
+ end
63
+ end
64
+
65
+ resources
66
+ end
67
+
68
+ # @param resources [Array<K8s::Resource>]
69
+ # @return [String]
70
+ def resources_output(resources)
71
+ yaml = ''
72
+ resources.each do |resource|
73
+ yaml << ::YAML.dump(stringify_hash(resource.to_hash))
74
+ end
75
+ return yaml unless $stdout.tty?
76
+
77
+ lexer = Rouge::Lexers::YAML.new
78
+ rouge = Rouge::Formatters::Terminal256.new(Rouge::Themes::Github.new)
79
+ rouge.format(lexer.lex(yaml))
80
+ end
81
+
82
+ # @return [RecursiveOpenStruct]
83
+ def variables_struct
84
+ return @variables_struct if @variables_struct
85
+
86
+ set_hash = {}
87
+ var_list.each do |var|
88
+ k, v = var.split("=", 2)
89
+ set_hash[k] = v
90
+ end
91
+ RecursiveOpenStruct.new(dotted_path_to_hash(set_hash))
92
+ end
93
+
94
+ def dotted_path_to_hash(hash)
95
+ hash.map do |pkey, pvalue|
96
+ pkey.to_s.split(".").reverse.inject(pvalue) do |value, key|
97
+ {key.to_sym => value}
98
+ end
99
+ end.inject(&:deep_merge)
100
+ end
101
+
102
+ # Stringifies all hash keys
103
+ # @return [Hash]
104
+ def stringify_hash(hash)
105
+ JSON.load(JSON.dump(hash))
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,57 @@
1
+ module Mortar
2
+ module ClientHelper
3
+ # @return [K8s::Client]
4
+ def client
5
+ return @client if @client
6
+
7
+ if ENV['KUBE_TOKEN'] && ENV['KUBE_CA'] && ENV['KUBE_SERVER']
8
+ @client = K8s::Client.new(K8s::Transport.config(build_kubeconfig_from_env))
9
+ elsif ENV['KUBECONFIG']
10
+ @client = K8s::Client.config(K8s::Config.load_file(ENV['KUBECONFIG']))
11
+ elsif File.exist?(File.join(Dir.home, '.kube', 'config'))
12
+ @client = K8s::Client.config(K8s::Config.load_file(File.join(Dir.home, '.kube', 'config')))
13
+ else
14
+ @client = K8s::Client.in_cluster_config
15
+ end
16
+ end
17
+
18
+ # @return [K8s::Config]
19
+ def build_kubeconfig_from_env
20
+ token = ENV['KUBE_TOKEN']
21
+ token = Base64.strict_decode64(token)
22
+
23
+ K8s::Config.new(
24
+ clusters: [
25
+ {
26
+ name: 'kubernetes',
27
+ cluster: {
28
+ server: ENV['KUBE_SERVER'],
29
+ certificate_authority_data: ENV['KUBE_CA']
30
+ }
31
+ }
32
+ ],
33
+ users: [
34
+ {
35
+ name: 'mortar',
36
+ user: {
37
+ token: token
38
+ }
39
+ }
40
+ ],
41
+ contexts: [
42
+ {
43
+ name: 'mortar',
44
+ context: {
45
+ cluster: 'kubernetes',
46
+ user: 'mortar'
47
+ }
48
+ }
49
+ ],
50
+ preferences: {},
51
+ current_context: 'mortar'
52
+ )
53
+ rescue ArgumentError
54
+ signal_usage_error "KUBE_TOKEN env doesn't seem to be base64 encoded!"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,8 @@
1
+ module Mortar
2
+ module TTYHelper
3
+ # @return [Pastel]
4
+ def pastel
5
+ @pastel ||= Pastel.new
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ require "clamp"
2
+ require_relative "fire_command"
3
+ require_relative "yank_command"
4
+
5
+ Clamp.allow_options_after_parameters = true
6
+
7
+ module Mortar
8
+ class RootCommand < Clamp::Command
9
+
10
+ banner "mortar - Kubernetes manifest shooter"
11
+
12
+ option ['-v', '--version'], :flag, "print mortar version" do
13
+ puts "mortar #{Mortar::VERSION}"
14
+ exit 0
15
+ end
16
+
17
+ subcommand "fire", "Fire a shot (of k8s manifests)", FireCommand
18
+ subcommand "yank", "Yank a shot (of k8s manifests)", YankCommand
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Mortar
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0.pre1"
3
3
  end
@@ -0,0 +1,40 @@
1
+ require_relative "command"
2
+ require_relative "mixins/client_helper"
3
+ require_relative "mixins/tty_helper"
4
+
5
+ module Mortar
6
+ class YankCommand < Mortar::Command
7
+ include Mortar::ClientHelper
8
+ include Mortar::TTYHelper
9
+
10
+ parameter "NAME", "deployment name"
11
+
12
+ option ["--force"], :flag, "use force"
13
+
14
+ def execute
15
+ unless force?
16
+ if $stdin.tty?
17
+ print "enter '#{pastel.cyan(name)}' to confirm yank: "
18
+ begin
19
+ signal_error("confirmation did not match #{pastel.cyan(name)}.") unless $stdin.gets.chomp == name
20
+ rescue Interrupt
21
+ puts
22
+ puts "Canceled"
23
+ return
24
+ end
25
+ else
26
+ signal_usage_error '--force required when running in a non-interactive mode'
27
+ end
28
+ end
29
+
30
+ K8s::Stack.new(
31
+ name, [],
32
+ debug: debug?,
33
+ label: LABEL,
34
+ checksum_annotation: CHECKSUM_ANNOTATION
35
+ ).prune(client, keep_resources: false)
36
+
37
+ puts "yanked #{pastel.cyan(name)} successfully!" if $stdout.tty?
38
+ end
39
+ end
40
+ end
data/lib/mortar.rb CHANGED
@@ -1,13 +1,16 @@
1
1
  require "clamp"
2
2
  require "deep_merge"
3
3
  require "mortar/version"
4
- require "mortar/command"
4
+ require "mortar/root_command"
5
5
 
6
6
  autoload :K8s, "k8s-client"
7
7
  autoload :YAML, "yaml"
8
8
  autoload :ERB, "erb"
9
9
  autoload :Rouge, "rouge"
10
10
  autoload :RecursiveOpenStruct, "recursive-open-struct"
11
+ autoload :Pastel, "pastel"
12
+
13
+ require "extensions/recursive_open_struct/each"
11
14
 
12
15
  module Mortar
13
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kontena-mortar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kontena, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-28 00:00:00.000000000 Z
11
+ date: 2018-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clamp
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
33
+ version: 0.4.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.3'
40
+ version: 0.4.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rouge
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pastel
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.2
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bundler
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -119,6 +133,7 @@ files:
119
133
  - ".drone.yml"
120
134
  - ".gitignore"
121
135
  - ".rspec"
136
+ - ".travis.yml"
122
137
  - Dockerfile
123
138
  - Gemfile
124
139
  - Gemfile.lock
@@ -126,12 +141,33 @@ files:
126
141
  - README.md
127
142
  - Rakefile
128
143
  - bin/mortar
144
+ - build/drone/create_release.sh
145
+ - build/drone/ubuntu_xenial.sh
146
+ - build/travis/macos.sh
147
+ - examples/basic/deployment.yml
148
+ - examples/basic/service.yml
149
+ - examples/force-deployment/deployment.yml.erb
150
+ - examples/overlays/echo-metal.yml
151
+ - examples/overlays/foo/pod.yml.erb
152
+ - examples/overlays/partial/echo.yml
153
+ - examples/overlays/prod/pod.yml
154
+ - examples/overlays/prod/svc.yml
155
+ - examples/templates/env_vars/pod.yml.erb
156
+ - examples/templates/variables/loops.yml.erb
157
+ - examples/templates/variables/pod-vars.yml.erb
129
158
  - kontena-mortar.gemspec
159
+ - kontena-mortar.png
160
+ - lib/extensions/recursive_open_struct/each.rb
130
161
  - lib/mortar.rb
131
162
  - lib/mortar/command.rb
132
- - lib/mortar/resource_helper.rb
163
+ - lib/mortar/fire_command.rb
164
+ - lib/mortar/mixins/client_helper.rb
165
+ - lib/mortar/mixins/resource_helper.rb
166
+ - lib/mortar/mixins/tty_helper.rb
167
+ - lib/mortar/root_command.rb
133
168
  - lib/mortar/version.rb
134
169
  - lib/mortar/yaml_file.rb
170
+ - lib/mortar/yank_command.rb
135
171
  homepage: https://github.com/kontena/mortar
136
172
  licenses:
137
173
  - Apache-2.0
@@ -147,9 +183,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
183
  version: '0'
148
184
  required_rubygems_version: !ruby/object:Gem::Requirement
149
185
  requirements:
150
- - - ">="
186
+ - - ">"
151
187
  - !ruby/object:Gem::Version
152
- version: '0'
188
+ version: 1.3.1
153
189
  requirements: []
154
190
  rubyforge_project:
155
191
  rubygems_version: 2.7.6