Commit 90f1d3d5 authored by Matt Butcher's avatar Matt Butcher

Add Chart tar reader, dir reader, tests.

parent 84e61c03
package chart
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/helm/helm/manifest"
"github.com/kubernetes/deployment-manager/log"
)
const (
preTemplates string = "templates/"
preHooks string = "hooks/"
preDocs string = "docs/"
)
// Chart represents a complete chart.
......@@ -13,31 +25,67 @@ import (
// A chart consists of the following parts:
//
// - Chart.yaml: In code, we refer to this as the Chartfile
// - manifests/*.yaml: The Kubernetes manifests
// - templates/*: The template directory
// - README.md: Optional README file
// - LICENSE: Optional license file
// - hooks/: Optional hooks registry
// - docs/: Optional docs directory
//
// On the Chart object, the manifests are sorted by type into a handful of
// recognized Kubernetes API v1 objects.
// Packed charts are stored in gzipped tar archives (.tgz). Unpackaged charts
// are directories where the directory name is the Chartfile.Name.
//
// TODO: Investigate treating these as unversioned.
type Chart struct {
Chartfile *Chartfile
// Optionally, a chart might also locate a provenance (.prov) file that it
// can use for cryptographic signing.
type Chart interface {
// Chartfile resturns a *Chartfile for this chart.
Chartfile() *Chartfile
// Dir returns a directory where the chart can be accessed.
Dir() string
Close() error
}
type dirChart struct {
chartfile *Chartfile
}
// Kind is a map of Kind to an array of manifests.
//
// For example, Kind["Pod"] has an array of Pod manifests.
Kind map[string][]*manifest.Manifest
func (d *dirChart) Chartfile() *Chartfile {
return d.chartfile
}
func (d *dirChart) Dir() string {
return "."
}
// Manifests is an array of Manifest objects.
Manifests []*manifest.Manifest
func (d *dirChart) Close() error {
return nil
}
// Load loads an entire chart.
type tarChart struct {
chartfile *Chartfile
tmpDir string
}
func (t *tarChart) Chartfile() *Chartfile {
return t.chartfile
}
func (t *tarChart) Dir() string {
return "."
}
func (t *tarChart) Close() error {
// Remove the temp directory.
return os.RemoveAll(t.tmpDir)
}
// LoadDir loads an entire chart from a directory.
//
// This includes the Chart.yaml (*Chartfile) and all of the manifests.
//
// If you are just reading the Chart.yaml file, it is substantially more
// performant to use LoadChartfile.
func Load(chart string) (*Chart, error) {
func LoadDir(chart string) (Chart, error) {
if fi, err := os.Stat(chart); err != nil {
return nil, err
} else if !fi.IsDir() {
......@@ -49,59 +97,103 @@ func Load(chart string) (*Chart, error) {
return nil, err
}
c := &Chart{
Chartfile: cf,
Kind: map[string][]*manifest.Manifest{},
c := &dirChart{
chartfile: cf,
}
return c, nil
}
// Load loads a chart from a chart archive.
//
// A chart archive is a gzipped tar archive that follows the Chart format
// specification.
func Load(archive string) (Chart, error) {
if fi, err := os.Stat(archive); err != nil {
return nil, err
} else if fi.IsDir() {
return nil, errors.New("cannot load a directory with chart.Load()")
}
ms, err := manifest.ParseDir(chart)
raw, err := os.Open(archive)
if err != nil {
return c, err
return nil, err
}
defer raw.Close()
c.attachManifests(ms)
unzipped, err := gzip.NewReader(raw)
if err != nil {
return nil, err
}
defer unzipped.Close()
return c, nil
untarred := tar.NewReader(unzipped)
return loadTar(untarred)
}
const (
// AnnFile is the annotation key for a file's origin.
AnnFile = "chart.helm.sh/file"
func loadTar(r *tar.Reader) (Chart, error) {
td, err := ioutil.TempDir("", "chart-")
if err != nil {
return nil, err
}
c := &tarChart{
chartfile: &Chartfile{},
tmpDir: td,
}
// AnnChartVersion is the annotation key for a chart's version.
AnnChartVersion = "chart.helm.sh/version"
firstDir := ""
// AnnChartDesc is the annotation key for a chart's description.
AnnChartDesc = "chart.helm.sh/description"
hdr, err := r.Next()
for err == nil {
log.Debug("Reading %s", hdr.Name)
// AnnChartName is the annotation key for a chart name.
AnnChartName = "chart.helm.sh/name"
)
// This is to prevent malformed tar attacks.
hdr.Name = filepath.Clean(hdr.Name)
// attachManifests sorts manifests into their respective categories, adding to the Chart.
func (c *Chart) attachManifests(manifests []*manifest.Manifest) {
c.Manifests = manifests
for _, m := range manifests {
c.Kind[m.Kind] = append(c.Kind[m.Kind], m)
if firstDir == "" {
fi := hdr.FileInfo()
if fi.IsDir() {
log.Debug("Discovered app named %s", hdr.Name)
firstDir = hdr.Name
} else {
log.Warn("Unexpected file at root of archive: %s", hdr.Name)
}
}
} else if strings.HasPrefix(hdr.Name, firstDir) {
log.Debug("Extracting %s to %s", hdr.Name, c.tmpDir)
// UnknownKinds returns a list of kinds that this chart contains, but which were not in the passed in array.
//
// A Chart will store all kinds that are given to it. This makes it possible to get a list of kinds that are not
// known beforehand.
func (c *Chart) UnknownKinds(known []string) []string {
lookup := make(map[string]bool, len(known))
for _, k := range known {
lookup[k] = true
// We know this has the prefix, so we know there won't be an error.
rel, _ := filepath.Rel(firstDir, hdr.Name)
// If tar record is a directory, create one in the tmpdir and return.
if hdr.FileInfo().IsDir() {
os.MkdirAll(filepath.Join(c.tmpDir, rel), 0755)
hdr, err = r.Next()
continue
}
u := []string{}
for n := range c.Kind {
if _, ok := lookup[n]; !ok {
u = append(u, n)
dest := filepath.Join(c.tmpDir, rel)
f, err := os.Create(filepath.Join(c.tmpDir, rel))
if err != nil {
log.Warn("Could not create %s: %s", dest, err)
hdr, err = r.Next()
continue
}
if _, err := io.Copy(f, r); err != nil {
log.Warn("Failed to copy %s: %s", dest, err)
}
f.Close()
} else {
log.Warn("Unexpected file outside of chart: %s", hdr.Name)
}
hdr, err = r.Next()
}
return u
if err != nil && err != io.EOF {
log.Warn("Unexpected error reading tar: %s", err)
c.Close()
return c, err
}
log.Info("Reached end of Tar file")
return c, nil
}
......@@ -2,116 +2,63 @@ package chart
import (
"testing"
)
const testfile = "../testdata/test-Chart.yaml"
const testchart = "../testdata/charts/kitchensink"
func TestLoad(t *testing.T) {
c, err := Load(testchart)
if err != nil {
t.Errorf("Failed to load chart: %s", err)
}
if c.Chartfile.Name != "kitchensink" {
t.Errorf("Expected chart name to be 'kitchensink'. Got '%s'.", c.Chartfile.Name)
}
if c.Chartfile.Dependencies[0].Version != "~10.21" {
d := c.Chartfile.Dependencies[0].Version
t.Errorf("Expected dependency 0 to have version '~10.21'. Got '%s'.", d)
}
if len(c.Kind["Pod"]) != 3 {
t.Errorf("Expected 3 pods, got %d", len(c.Kind["Pod"]))
}
if len(c.Kind["ReplicationController"]) == 0 {
t.Error("No RCs found")
}
if len(c.Kind["Namespace"]) == 0 {
t.Errorf("No namespaces found")
}
if len(c.Kind["Secret"]) == 0 {
t.Error("Is it secret? Is it safe? NO!")
}
"github.com/kubernetes/deployment-manager/log"
)
if len(c.Kind["PersistentVolume"]) == 0 {
t.Errorf("No volumes.")
}
const testfile = "testdata/frobnitz/Chart.yaml"
const testdir = "testdata/frobnitz/"
const testarchive = "testdata/frobnitz-0.0.1.tgz"
const testill = "testdata/ill-1.2.3.tgz"
if len(c.Kind["Service"]) == 0 {
t.Error("No service. Just like [insert mobile provider name here]")
}
func init() {
log.IsDebugging = true
}
func TestLoadChart(t *testing.T) {
f, err := LoadChartfile(testfile)
func TestLoadDir(t *testing.T) {
c, err := LoadDir(testdir)
if err != nil {
t.Errorf("Error loading %s: %s", testfile, err)
}
if f.Name != "alpine-pod" {
t.Errorf("Expected alpine-pod, got %s", f.Name)
}
if len(f.Maintainers) != 2 {
t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers))
}
if len(f.Dependencies) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(f.Dependencies))
}
if f.Dependencies[1].Name != "bar" {
t.Errorf("Expected second dependency to be bar: %q", f.Dependencies[1].Name)
t.Errorf("Failed to load chart: %s", err)
}
if f.PreInstall["mykeys"] != "generate-keypair foo" {
t.Errorf("Expected map value for mykeys.")
if c.Chartfile().Name != "frobnitz" {
t.Errorf("Expected chart name to be 'frobnitz'. Got '%s'.", c.Chartfile().Name)
}
if f.Source[0] != "https://example.com/helm" {
t.Errorf("Expected https://example.com/helm, got %s", f.Source)
if c.Chartfile().Dependencies[0].Version != "^3" {
d := c.Chartfile().Dependencies[0].Version
t.Errorf("Expected dependency 0 to have version '^3'. Got '%s'.", d)
}
}
func TestVersionOK(t *testing.T) {
f, err := LoadChartfile(testfile)
func TestLoad(t *testing.T) {
c, err := Load(testarchive)
if err != nil {
t.Errorf("Error loading %s: %s", testfile, err)
t.Errorf("Failed to load chart: %s", err)
return
}
defer c.Close()
// These are canaries. The SemVer package exhuastively tests the
// various permutations. This will alert us if we wired it up
// incorrectly.
d := f.Dependencies[1]
if d.VersionOK("1.0.0") {
t.Errorf("1.0.0 should have been marked out of range")
if c.Chartfile() == nil {
t.Error("No chartfile was loaded.")
return
}
if !d.VersionOK("1.2.3") {
t.Errorf("Version 1.2.3 should have been marked in-range")
if c.Chartfile().Name != "frobnitz" {
t.Errorf("Expected name to be frobnitz, got %q", c.Chartfile().Name)
}
}
func TestUnknownKinds(t *testing.T) {
known := []string{"Pod"}
c, err := Load(testchart)
func TestLoadIll(t *testing.T) {
c, err := Load(testill)
if err != nil {
t.Errorf("Failed to load chart: %s", err)
return
}
defer c.Close()
unknown := c.UnknownKinds(known)
if len(unknown) < 5 {
t.Errorf("Expected at least 5 unknown chart types, got %d.", len(unknown))
}
for _, k := range unknown {
if k == "Pod" {
t.Errorf("Pod is not an unknown kind.")
}
if c.Chartfile() == nil {
t.Error("No chartfile was loaded.")
return
}
}
package chart
import (
"io/ioutil"
"os"
"testing"
)
var testChart = `#helm:generate foo
name: frobniz
description: This is a frobniz.
version: 1.2.3-alpha.1+12345
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- name: The Helm Team
email: helm@example.com
- name: Someone Else
email: nobody@example.com
source: https://example.com/foo/bar
home: http://example.com
dependencies:
- name: thingerbob
location: https://example.com/charts/thingerbob-3.2.1.tgz
version: ^3
environment:
- name: Kubernetes
version: ~1.1
extensions:
- extensions/v1beta1
- extensions/v1beta1/daemonset
apiGroups:
- 3rdParty
`
func TestLoadChartfile(t *testing.T) {
out, err := ioutil.TempFile("", "chartfile-")
f, err := LoadChartfile(testfile)
if err != nil {
t.Fatal(err)
t.Errorf("Failed to open %s: %s", testfile, err)
return
}
if len(f.Environment[0].Extensions) != 2 {
t.Errorf("Expected two extensions, got %d", len(f.Environment[0].Extensions))
}
if f.Name != "frobnitz" {
t.Errorf("Expected frobnitz, got %s", f.Name)
}
tname := out.Name()
defer func() {
os.Remove(tname)
}()
out.Write([]byte(testChart))
out.Close()
if len(f.Maintainers) != 2 {
t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers))
}
c, err := LoadChartfile(tname)
if len(f.Dependencies) != 1 {
t.Errorf("Expected 2 dependencies, got %d", len(f.Dependencies))
}
if f.Dependencies[0].Name != "thingerbob" {
t.Errorf("Expected second dependency to be thingerbob: %q", f.Dependencies[0].Name)
}
if f.Source[0] != "https://example.com/helm" {
t.Errorf("Expected https://example.com/helm, got %s", f.Source)
}
}
func TestVersionOK(t *testing.T) {
f, err := LoadChartfile(testfile)
if err != nil {
t.Errorf("Failed to open %s: %s", tname, err)
return
t.Errorf("Error loading %s: %s", testfile, err)
}
if len(c.Environment[0].Extensions) != 2 {
t.Errorf("Expected two extensions, got %d", len(c.Environment[0].Extensions))
// These are canaries. The SemVer package exhuastively tests the
// various permutations. This will alert us if we wired it up
// incorrectly.
d := f.Dependencies[0]
if d.VersionOK("1.0.0") {
t.Errorf("1.0.0 should have been marked out of range")
}
if !d.VersionOK("3.2.3") {
t.Errorf("Version 3.2.3 should have been marked in-range")
}
}
/* Package chart implements the Chart format.
This package provides tools for working with the Chart format, including the
Chartfile (chart.yaml) and compressed chart archives.
*/
package chart
The testdata directory here holds charts that match the specification.
The `fromnitz/` directory contains a chart that matches the chart
specification.
The `frobnitz-0.0.1.tgz` file is an archive of the `frobnitz` directory.
The `ill` chart and directory is a chart that is not 100% compatible,
but which should still be parseable.
#helm:generate foo
name: frobnitz
description: This is a frobniz.
version: "1.2.3-alpha.1+12345"
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- name: The Helm Team
email: helm@example.com
- name: Someone Else
email: nobody@example.com
source:
- https://example.com/foo/bar
home: http://example.com
dependencies:
- name: thingerbob
location: https://example.com/charts/thingerbob-3.2.1.tgz
version: ^3
environment:
- name: Kubernetes
version: ~1.1
extensions:
- extensions/v1beta1
- extensions/v1beta1/daemonset
apiGroups:
- 3rdParty
This diff is collapsed.
# Frobnitz
This is an example chart.
## Usage
This is an example. It has no usage.
## Development
For developer info, see the top-level repository.
This is a placeholder for documentation.
# Google Cloud Deployment Manager template
resources:
- name: nfs-disk
type: compute.v1.disk
properties:
zone: us-central1-b
sizeGb: 200
- name: mysql-disk
type: compute.v1.disk
properties:
zone: us-central1-b
sizeGb: 200
#helm:generate dm_template
{% set PROPERTIES = properties or {} %}
{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %}
{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %}
{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %}
{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %}
{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %}
{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %}
{% set NGINX = PROPERTIES['nginx'] or {} %}
{% set NGINX_PORT = 80 %}
{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %}
{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %}
{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %}
{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %}
{% set MYSQL = PROPERTIES['mysql'] or {} %}
{% set MYSQL_PORT = MYSQL['port'] or 3306 %}
{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %}
{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %}
{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %}
resources:
- name: nfs
type: github.com/kubernetes/application-dm-templates/storage/nfs:v1
properties:
ip: {{ NFS_SERVER_IP }}
port: {{ NFS_SERVER_PORT }}
disk: {{ NFS_SERVER_DISK }}
fstype: {{NFS_SERVER_DISK_FSTYPE }}
- name: nginx
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_port: {{ NGINX_PORT }}
container_port: {{ NGINX_PORT }}
replicas: {{ NGINX_REPLICAS }}
external_service: true
image: gcr.io/{{ PROJECT }}/nginx:latest
volumes:
- mount_path: /var/www/html
persistentVolumeClaim:
claimName: nfs
- name: mysql
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_port: {{ MYSQL_PORT }}
container_port: {{ MYSQL_PORT }}
replicas: 1
image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: {{ MYSQL_PASSWORD }}
volumes:
- mount_path: /var/lib/mysql
gcePersistentDisk:
pdName: {{ MYSQL_DISK }}
fsType: {{ MYSQL_DISK_FSTYPE }}
- name: wordpress-php
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_name: wordpress-php
service_port: {{ WORDPRESS_PHP_PORT }}
container_port: {{ WORDPRESS_PHP_PORT }}
replicas: 2
image: wordpress:fpm
env:
- name: WORDPRESS_DB_PASSWORD
value: {{ MYSQL_PASSWORD }}
- name: WORDPRESS_DB_HOST
value: mysql-service
volumes:
- mount_path: /var/www/html
persistentVolumeClaim:
claimName: nfs
info:
title: Wordpress
description: |
Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service.
The nginx service and the Wordpress-php service both use NFS to share files.
properties:
project:
type: string
default: dm-k8s-testing
description: Project location to load the images from.
nfs-service:
type: object
properties:
ip:
type: string
default: 10.0.253.247
description: The IP of the NFS service.
port:
type: int
default: 2049
description: The port of the NFS service.
disk:
type: string
default: nfs-disk
description: The name of the persistent disk the NFS service uses.
fstype:
type: string
default: ext4
description: The filesystem the disk of the NFS service uses.
nginx:
type: object
properties:
replicas:
type: int
default: 2
description: The number of replicas for the nginx service.
wordpress-php:
type: object
properties:
replicas:
type: int
default: 2
description: The number of replicas for the wordpress-php service.
port:
type: int
default: 9000
description: The port the wordpress-php service runs on.
mysql:
type: object
properties:
port:
type: int
default: 3306
description: The port the MySQL service runs on.
password:
type: string
default: mysql-password
description: The root password of the MySQL service.
disk:
type: string
default: mysql-disk
description: The name of the persistent disk the MySQL service uses.
fstype:
type: string
default: ext4
description: The filesystem the disk of the MySQL service uses.
imports:
- path: wordpress.jinja
resources:
- name: wordpress
type: wordpress.jinja
#helm:generate foo
name: ill
description: This is a frobniz.
version: "1.2.3-alpha.1+12345"
keywords:
- ill
- sprocket
- dodad
maintainers:
- name: The Helm Team
email: helm@example.com
- name: Someone Else
email: nobody@example.com
source:
- https://example.com/foo/bar
home: http://example.com
dependencies:
- name: thingerbob
location: https://example.com/charts/thingerbob-3.2.1.tgz
version: ^3
environment:
- name: Kubernetes
version: ~1.1
extensions:
- extensions/v1beta1
- extensions/v1beta1/daemonset
apiGroups:
- 3rdParty
This diff is collapsed.
# Frobnitz
This is an example chart.
## Usage
This is an example. It has no usage.
## Development
For developer info, see the top-level repository.
This is a placeholder for documentation.
# Google Cloud Deployment Manager template
resources:
- name: nfs-disk
type: compute.v1.disk
properties:
zone: us-central1-b
sizeGb: 200
- name: mysql-disk
type: compute.v1.disk
properties:
zone: us-central1-b
sizeGb: 200
#helm:generate dm_template
{% set PROPERTIES = properties or {} %}
{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %}
{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %}
{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %}
{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %}
{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %}
{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %}
{% set NGINX = PROPERTIES['nginx'] or {} %}
{% set NGINX_PORT = 80 %}
{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %}
{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %}
{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %}
{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %}
{% set MYSQL = PROPERTIES['mysql'] or {} %}
{% set MYSQL_PORT = MYSQL['port'] or 3306 %}
{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %}
{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %}
{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %}
resources:
- name: nfs
type: github.com/kubernetes/application-dm-templates/storage/nfs:v1
properties:
ip: {{ NFS_SERVER_IP }}
port: {{ NFS_SERVER_PORT }}
disk: {{ NFS_SERVER_DISK }}
fstype: {{NFS_SERVER_DISK_FSTYPE }}
- name: nginx
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_port: {{ NGINX_PORT }}
container_port: {{ NGINX_PORT }}
replicas: {{ NGINX_REPLICAS }}
external_service: true
image: gcr.io/{{ PROJECT }}/nginx:latest
volumes:
- mount_path: /var/www/html
persistentVolumeClaim:
claimName: nfs
- name: mysql
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_port: {{ MYSQL_PORT }}
container_port: {{ MYSQL_PORT }}
replicas: 1
image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: {{ MYSQL_PASSWORD }}
volumes:
- mount_path: /var/lib/mysql
gcePersistentDisk:
pdName: {{ MYSQL_DISK }}
fsType: {{ MYSQL_DISK_FSTYPE }}
- name: wordpress-php
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
properties:
service_name: wordpress-php
service_port: {{ WORDPRESS_PHP_PORT }}
container_port: {{ WORDPRESS_PHP_PORT }}
replicas: 2
image: wordpress:fpm
env:
- name: WORDPRESS_DB_PASSWORD
value: {{ MYSQL_PASSWORD }}
- name: WORDPRESS_DB_HOST
value: mysql-service
volumes:
- mount_path: /var/www/html
persistentVolumeClaim:
claimName: nfs
info:
title: Wordpress
description: |
Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service.
The nginx service and the Wordpress-php service both use NFS to share files.
properties:
project:
type: string
default: dm-k8s-testing
description: Project location to load the images from.
nfs-service:
type: object
properties:
ip:
type: string
default: 10.0.253.247
description: The IP of the NFS service.
port:
type: int
default: 2049
description: The port of the NFS service.
disk:
type: string
default: nfs-disk
description: The name of the persistent disk the NFS service uses.
fstype:
type: string
default: ext4
description: The filesystem the disk of the NFS service uses.
nginx:
type: object
properties:
replicas:
type: int
default: 2
description: The number of replicas for the nginx service.
wordpress-php:
type: object
properties:
replicas:
type: int
default: 2
description: The number of replicas for the wordpress-php service.
port:
type: int
default: 9000
description: The port the wordpress-php service runs on.
mysql:
type: object
properties:
port:
type: int
default: 3306
description: The port the MySQL service runs on.
password:
type: string
default: mysql-password
description: The root password of the MySQL service.
disk:
type: string
default: mysql-disk
description: The name of the persistent disk the MySQL service uses.
fstype:
type: string
default: ext4
description: The filesystem the disk of the MySQL service uses.
imports:
- path: wordpress.jinja
resources:
- name: wordpress
type: wordpress.jinja
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment