diff --git a/actions/init.go b/actions/init.go index fcb000ab37453853e2d95736018d4a1aa3eb7a2e..5ef2f716b810f76c8b5b8327568f3a27a50c31e5 100644 --- a/actions/init.go +++ b/actions/init.go @@ -37,8 +37,11 @@ func RunInit(m map[string]interface{}) error { return i.Run() } +type appLoadFn func(fs afero.Fs, root string, skipFindRoot bool) (app.App, error) + type appInitFn func(fs afero.Fs, name, rootPath, k8sSpecFlag, serverURI, namespace string, registries []registry.Registry) error -type initIncubatorFn func() (registry.Registry, error) + +type initIncubatorFn func(app.App) (registry.Registry, error) // Init creates a component namespace type Init struct { @@ -51,6 +54,7 @@ type Init struct { skipDefaultRegistries bool appInitFn appInitFn + appLoadFn appLoadFn initIncubatorFn initIncubatorFn } @@ -68,6 +72,7 @@ func NewInit(m map[string]interface{}) (*Init, error) { skipDefaultRegistries: ol.loadBool(OptionSkipDefaultRegistries), appInitFn: appinit.Init, + appLoadFn: app.Load, initIncubatorFn: initIncubator, } @@ -83,7 +88,12 @@ func (i *Init) Run() error { var registries []registry.Registry if !i.skipDefaultRegistries { - gh, err := i.initIncubatorFn() + a, err := i.appLoadFn(i.fs, i.rootPath, true) + if err != nil { + return err + } + + gh, err := i.initIncubatorFn(a) if err != nil { return err } @@ -102,10 +112,12 @@ func (i *Init) Run() error { ) } -func initIncubator() (registry.Registry, error) { - return registry.NewGitHub(&app.RegistryRefSpec{ - Name: "incubator", - Protocol: registry.ProtocolGitHub, - URI: defaultIncubatorURI, - }) +func initIncubator(a app.App) (registry.Registry, error) { + return registry.NewGitHub( + a, + &app.RegistryRefSpec{ + Name: "incubator", + Protocol: registry.ProtocolGitHub, + URI: defaultIncubatorURI, + }) } diff --git a/actions/init_test.go b/actions/init_test.go index ac5375f86b3a75acc49e4aacc63930fca6752389..6a77915ec02fa3bdbd2e118cf3cdde20cfafd361 100644 --- a/actions/init_test.go +++ b/actions/init_test.go @@ -18,6 +18,7 @@ package actions import ( "testing" + "github.com/ksonnet/ksonnet/metadata/app" amocks "github.com/ksonnet/ksonnet/metadata/app/mocks" "github.com/ksonnet/ksonnet/pkg/registry" rmocks "github.com/ksonnet/ksonnet/pkg/registry/mocks" @@ -85,7 +86,11 @@ func TestInit(t *testing.T) { return nil } - a.initIncubatorFn = func() (registry.Registry, error) { + a.appLoadFn = func(fs afero.Fs, root string, skipFindRoot bool) (app.App, error) { + return appMock, nil + } + + a.initIncubatorFn = func(a app.App) (registry.Registry, error) { r := &rmocks.Registry{} r.On("Protocol").Return("github") r.On("URI").Return("github.com/ksonnet/parts/tree/master/incubator") diff --git a/cmd/root.go b/cmd/root.go index cd8b7a2c6ce84383c116ffaf2781f7ed404809a7..7b3f7a3cc4c024eaccdc70c67d0f7158068d8d3b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -102,7 +102,7 @@ application configuration to remote clusters. isInit = true } - ka, err = app.Load(appFs, wd) + ka, err = app.Load(appFs, wd, false) if err != nil && isInit { return err } diff --git a/e2e/init_test.go b/e2e/init_test.go index aa3c7afb396aebcacb0bc24de6ef3824fac5c92d..22d2b43f4db7c137ebf8f7373bef5547dfdfa8f8 100644 --- a/e2e/init_test.go +++ b/e2e/init_test.go @@ -1,3 +1,18 @@ +// Copyright 2018 The ksonnet authors +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + // +build e2e package e2e @@ -22,6 +37,10 @@ var _ = Describe("ks init", func() { o := a.registryList() assertOutput(filepath.Join("init", "registry-output.txt"), o.stdout) }) + + It("creates a gitignore", func() { + assertContents(filepath.Join("init", "gitignore"), filepath.Join(a.dir, ".gitignore")) + }) }) Context("without default registries", func() { diff --git a/e2e/pkg_test.go b/e2e/pkg_test.go index 32d634b1f8702cf92184feeefac41a44ac9cc980..488d8c7b36279f877fd28aa8959a1b6b8cc86c3c 100644 --- a/e2e/pkg_test.go +++ b/e2e/pkg_test.go @@ -18,6 +18,7 @@ package e2e import ( + "os" "path/filepath" . "github.com/onsi/ginkgo" @@ -101,6 +102,19 @@ var _ = Describe("ks pkg", func() { assertExitStatus(o, 0) assertOutput("pkg/list/output.txt", o.stdout) }) + + Context("git spec cache has been been deleted", func() { + JustBeforeEach(func() { + err := os.RemoveAll(filepath.Join(a.dir, ".ksonnet")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("generates spec cache", func() { + o := a.runKs("pkg", "list") + assertExitStatus(o, 0) + assertOutput("pkg/list/output.txt", o.stdout) + }) + }) }) Context("use", func() { diff --git a/e2e/show_test.go b/e2e/show_test.go index 5896f711771214745b31b7d7957996005593acfa..bbaadfd33dc3eec8bd4537f28641074e60084942 100644 --- a/e2e/show_test.go +++ b/e2e/show_test.go @@ -18,7 +18,11 @@ package e2e import ( + "os" + "path/filepath" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) var _ = Describe("ks show", func() { @@ -35,4 +39,17 @@ var _ = Describe("ks show", func() { assertOutput("show/output.txt", o.stdout) }) + Context("lib does not exists", func() { + JustBeforeEach(func() { + err := os.RemoveAll(filepath.Join(a.dir, "lib")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("generates spec cache", func() { + o := a.runKs("show", "default") + assertExitStatus(o, 0) + assertOutput("show/output.txt", o.stdout) + }) + }) + }) diff --git a/e2e/testdata/output/init/gitignore b/e2e/testdata/output/init/gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a67900298c57d166cc5364501bb2d21992282353 --- /dev/null +++ b/e2e/testdata/output/init/gitignore @@ -0,0 +1,2 @@ +/lib +/.ksonnet/registries diff --git a/metadata/app/app.go b/metadata/app/app.go index 5f9b85a200317e514ff73f3164da6dc031d9080a..e82cbb2acf2015c35443bfa353d4c152dc3974e6 100644 --- a/metadata/app/app.go +++ b/metadata/app/app.go @@ -71,15 +71,19 @@ type App interface { } // Load loads the application configuration. -func Load(fs afero.Fs, cwd string) (App, error) { - appRoot, err := findRoot(fs, cwd) - if err != nil { - return nil, err +func Load(fs afero.Fs, cwd string, skipFindRoot bool) (App, error) { + appRoot := cwd + if !skipFindRoot { + var err error + appRoot, err = findRoot(fs, cwd) + if err != nil { + return nil, err + } } spec, err := read(fs, appRoot) if err != nil { - return nil, err + return NewApp010(fs, appRoot), nil } switch spec.APIVersion { diff --git a/metadata/manager.go b/metadata/manager.go index 99b0d4f1033029208bf75128e5908427de44756a..fcdf5be7ea86cb23363ef677653ffe074ddcd20b 100644 --- a/metadata/manager.go +++ b/metadata/manager.go @@ -146,7 +146,7 @@ func (m *manager) Root() string { } func (m *manager) App() (app.App, error) { - return app.Load(m.appFS, m.rootPath) + return app.Load(m.appFS, m.rootPath, false) } func (m *manager) LibPaths() (envPath, vendorPath string) { diff --git a/pkg/appinit/init.go b/pkg/appinit/init.go index c2006a1844114d50931bcd12c6239d9d86f8fcdd..adc1a74094b25eb5fb55986cbe986f7b92bd057d 100644 --- a/pkg/appinit/init.go +++ b/pkg/appinit/init.go @@ -65,7 +65,7 @@ func (i *initApp) Run() error { } // Load application. - a, err := app.Load(i.fs, i.rootPath) + a, err := app.Load(i.fs, i.rootPath, false) if err != nil { return err } @@ -187,6 +187,10 @@ func (i *initApp) createAppDirTree() error { path string content []byte }{ + { + filepath.Join(i.rootPath, ".gitignore"), + ignoreData, + }, { filepath.Join(i.rootPath, "components", "params.libsonnet"), component.GenParamsContent(), @@ -210,3 +214,7 @@ func (i *initApp) createAppDirTree() error { return nil } + +var ignoreData = []byte(`/lib +/.ksonnet/registries +`) diff --git a/pkg/appinit/init_test.go b/pkg/appinit/init_test.go index 8fd14eb8b1e47d44a432bc5f43b7bb54988165d7..291bbc20660b242765fbbdb26c0d82036956dcaf 100644 --- a/pkg/appinit/init_test.go +++ b/pkg/appinit/init_test.go @@ -94,6 +94,7 @@ func TestInit(t *testing.T) { func checkApp(t *testing.T, fs afero.Fs, rootPath, version, namespace string) { expectedDirs := []string{ + ".gitignore", "app.yaml", filepath.Join(".ksonnet", "registries", "testdata", "registry.yaml"), filepath.Join("components", "params.libsonnet"), diff --git a/pkg/registry/add.go b/pkg/registry/add.go index cc1cd8e49019b327062f9e56b9ef85d0264fb111..f6b231221466d1daa03a111ca89272945f54491b 100644 --- a/pkg/registry/add.go +++ b/pkg/registry/add.go @@ -16,8 +16,6 @@ package registry import ( - "path/filepath" - "github.com/ksonnet/ksonnet/metadata/app" "github.com/pkg/errors" "github.com/spf13/afero" @@ -37,7 +35,7 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec switch protocol { case ProtocolGitHub: - r, err = githubFactory(initSpec) + r, err = githubFactory(a, initSpec) case ProtocolFilesystem: r, err = NewFs(a, initSpec) default: @@ -54,7 +52,7 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec } // Retrieve the contents of registry. - registrySpec, err := getOrCacheRegistry(a, r) + registrySpec, err := r.FetchRegistrySpec() if err != nil { return nil, errors.Wrap(err, "cache registry") } @@ -62,46 +60,6 @@ func Add(a app.App, name, protocol, uri, version string, isOverride bool) (*Spec return registrySpec, nil } -func getOrCacheRegistry(a app.App, gh Registry) (*Spec, error) { - // Check local disk cache. - registrySpecFile := makePath(a, gh) - registrySpec, exists, err := load(a, registrySpecFile) - if err != nil { - return nil, errors.Wrap(err, "load registry spec file") - } - - if !exists { - // If failed, use the protocol to try to retrieve app specification. - registrySpec, err = gh.FetchRegistrySpec() - if err != nil { - return nil, err - } - - registrySpecBytes, err := registrySpec.Marshal() - if err != nil { - return nil, err - } - - // NOTE: We call mkdir after getting the registry spec, since a - // network call might fail and leave this half-initialized empty - // directory. - registrySpecDir := filepath.Join(root(a), gh.RegistrySpecDir()) - err = a.Fs().MkdirAll(registrySpecDir, app.DefaultFolderPermissions) - if err != nil { - return nil, err - } - - err = afero.WriteFile(a.Fs(), registrySpecFile, registrySpecBytes, app.DefaultFilePermissions) - if err != nil { - return nil, err - } - } else if err != nil { - return nil, err - } - - return registrySpec, nil -} - func load(a app.App, path string) (*Spec, bool, error) { exists, err := afero.Exists(a.Fs(), path) if err != nil { diff --git a/pkg/registry/add_test.go b/pkg/registry/add_test.go index a48f9820914c1e31bce7e34c4a4ba500c7a03c65..b1962de0fb24355bb5acd523e7fa078fc4919246 100644 --- a/pkg/registry/add_test.go +++ b/pkg/registry/add_test.go @@ -69,8 +69,8 @@ func TestAdd(t *testing.T) { Return(registryContent, nil, nil) ghOpt := GitHubClient(ghMock) - githubFactory = func(registryRef *app.RegistryRefSpec) (*GitHub, error) { - return NewGitHub(registryRef, ghOpt) + githubFactory = func(a app.App, registryRef *app.RegistryRefSpec) (*GitHub, error) { + return NewGitHub(a, registryRef, ghOpt) } spec, err := Add(appMock, "new", ProtocolGitHub, "github.com/foo/bar", "", true) diff --git a/pkg/registry/cache.go b/pkg/registry/cache.go index ee56c09ebee5feb8db9608016c56ddb735ae9d93..759c2893e1fe3da1d180c877e61f4345cf44428a 100644 --- a/pkg/registry/cache.go +++ b/pkg/registry/cache.go @@ -26,7 +26,7 @@ import ( "github.com/spf13/afero" ) -// CacheDependency caches registry dependencies. +// CacheDependency vendors registry dependencies. // TODO: create unit tests for this once mocks for this package are // worked out. func CacheDependency(a app.App, d pkg.Descriptor, customName string) error { @@ -80,7 +80,6 @@ func CacheDependency(a app.App, d pkg.Descriptor, customName string) error { // Add library to app specification, but wait to write it out until // the end, in case one of the network calls fails. - log.Infof("Retrieved %d files", len(files)) for _, dir := range directories { diff --git a/pkg/registry/cache_test.go b/pkg/registry/cache_test.go index 4e8ceed46fddf14fca6053cf3af2056482ead279..e26fedc320ff46ee7f2723f949724f18cd3e1c13 100644 --- a/pkg/registry/cache_test.go +++ b/pkg/registry/cache_test.go @@ -16,62 +16,36 @@ package registry import ( + "path/filepath" "testing" "github.com/ksonnet/ksonnet/metadata/app" amocks "github.com/ksonnet/ksonnet/metadata/app/mocks" "github.com/ksonnet/ksonnet/pkg/pkg" - ghutil "github.com/ksonnet/ksonnet/pkg/util/github" - "github.com/ksonnet/ksonnet/pkg/util/github/mocks" + "github.com/ksonnet/ksonnet/pkg/util/test" "github.com/spf13/afero" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func Test_CacheDependency(t *testing.T) { withApp(t, func(a *amocks.App, fs afero.Fs) { + test.StageDir(t, fs, "incubator", filepath.Join("/work", "incubator")) + libraries := app.LibraryRefSpecs{} a.On("Libraries").Return(libraries, nil) registries := app.RegistryRefSpecs{ "incubator": &app.RegistryRefSpec{ Name: "incubator", - Protocol: ProtocolGitHub, - URI: "github.com/foo/bar/tree/master/incubator", - GitVersion: &app.GitVersionSpec{ - CommitSHA: "54321", - RefSpec: "master", - }, + Protocol: ProtocolFilesystem, + URI: "file:///work/incubator", }, } a.On("Registries").Return(registries, nil) - ghMock := &mocks.GitHub{} - ghMock.On("CommitSHA1", mock.Anything, mock.Anything, "master").Return("54321", nil) - - repo := ghutil.Repo{Org: "foo", Repo: "bar"} - mockPartFs(t, repo, ghMock, "incubator/apache", "54321") - - registryContent := buildContent(t, registryYAMLFile) - ghMock.On( - "Contents", - mock.Anything, - registryYAMLFile, - "40285d8a14f1ac5787e405e1023cf0c07f6aa28c"). - Return(registryContent, nil, nil) - - ghOpt := GitHubClient(ghMock) - githubFactory = func(registryRef *app.RegistryRefSpec) (*GitHub, error) { - return NewGitHub(registryRef, ghOpt) - } - library := &app.LibraryRefSpec{ Name: "apache", Registry: "incubator", - GitVersion: &app.GitVersionSpec{ - CommitSHA: "54321", - RefSpec: "master", - }, } a.On("UpdateLib", "apache", library).Return(nil) @@ -79,5 +53,7 @@ func Test_CacheDependency(t *testing.T) { err := CacheDependency(a, d, "") require.NoError(t, err) + + test.AssertExists(t, fs, filepath.Join(a.Root(), "vendor", "incubator", "apache", "parts.yaml")) }) } diff --git a/pkg/registry/fs.go b/pkg/registry/fs.go index 1165abbec70a1c8c31057684479900c5c650fa51..0fd8a687ed3fabf484d33b5fa39bb6a7ba4ccf76 100644 --- a/pkg/registry/fs.go +++ b/pkg/registry/fs.go @@ -118,6 +118,10 @@ func (fs *Fs) ResolveLibrarySpec(partName, libRefSpec string) (*parts.Spec, erro // ResolveLibrary fetches the part and creates a parts spec and library ref spec. func (fs *Fs) ResolveLibrary(partName, partAlias, libRefSpec string, onFile ResolveFile, onDir ResolveDirectory) (*parts.Spec, *app.LibraryRefSpec, error) { + if partAlias == "" { + partAlias = partName + } + partRoot := filepath.Join(fs.RegistrySpecDir(), partName) parentDir := filepath.Dir(fs.RegistrySpecDir()) diff --git a/pkg/registry/github.go b/pkg/registry/github.go index c9bf37fc5e1416c7c31d190929e74a2dccb547b8..74abb9d4c772728c6e6c86411c6e1aac8aaea998 100644 --- a/pkg/registry/github.go +++ b/pkg/registry/github.go @@ -20,12 +20,14 @@ import ( "fmt" "net/url" "path" + "path/filepath" "strings" "github.com/ksonnet/ksonnet/metadata/app" "github.com/ksonnet/ksonnet/pkg/parts" "github.com/ksonnet/ksonnet/pkg/util/github" "github.com/pkg/errors" + "github.com/spf13/afero" ) const ( @@ -37,12 +39,12 @@ var ( // errInvalidURI is an invalid github uri error. errInvalidURI = fmt.Errorf("Invalid GitHub URI: try navigating in GitHub to the URI of the folder containing the 'yaml', and using that URI instead. Generally, this URI should be of the form 'github.com/{organization}/{repository}/tree/{branch}/[path-to-directory]'") - githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) { - return NewGitHub(spec) + githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) { + return NewGitHub(a, spec) } ) -type ghFactoryFn func(spec *app.RegistryRefSpec) (*GitHub, error) +type ghFactoryFn func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) // GitHubClient is an option for the setting a github client. func GitHubClient(c github.GitHub) GitHubOpt { @@ -56,6 +58,7 @@ type GitHubOpt func(*GitHub) // GitHub is a Github Registry type GitHub struct { + app app.App name string hd *hubDescriptor ghClient github.GitHub @@ -63,12 +66,13 @@ type GitHub struct { } // NewGitHub creates an instance of GitHub. -func NewGitHub(registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, error) { +func NewGitHub(a app.App, registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, error) { if registryRef == nil { return nil, errors.New("registry ref is nil") } gh := &GitHub{ + app: a, name: registryRef.Name, spec: registryRef, ghClient: github.DefaultClient, @@ -80,19 +84,21 @@ func NewGitHub(registryRef *app.RegistryRefSpec, opts ...GitHubOpt) (*GitHub, er } gh.hd = hd - for _, opt := range opts { - opt(gh) - } + if gh.spec.GitVersion == nil || gh.spec.GitVersion.CommitSHA == "" { + for _, opt := range opts { + opt(gh) + } - ctx := context.Background() - sha, err := gh.ghClient.CommitSHA1(ctx, hd.Repo(), hd.refSpec) - if err != nil { - return nil, errors.Wrap(err, "unable to find SHA1 for repo") - } + ctx := context.Background() + sha, err := gh.ghClient.CommitSHA1(ctx, hd.Repo(), hd.refSpec) + if err != nil { + return nil, errors.Wrap(err, "unable to find SHA1 for repo") + } - gh.spec.GitVersion = &app.GitVersionSpec{ - RefSpec: hd.refSpec, - CommitSHA: sha, + gh.spec.GitVersion = &app.GitVersionSpec{ + RefSpec: hd.refSpec, + CommitSHA: sha, + } } return gh, nil @@ -133,6 +139,47 @@ func (gh *GitHub) RegistrySpecFilePath() string { // FetchRegistrySpec fetches the registry spec. func (gh *GitHub) FetchRegistrySpec() (*Spec, error) { + // Check local disk cache. + registrySpecFile := makePath(gh.app, gh) + registrySpec, exists, err := load(gh.app, registrySpecFile) + if err != nil { + return nil, errors.Wrap(err, "load registry spec file") + } + + if !exists { + // If failed, use the protocol to try to retrieve app specification. + registrySpec, err = gh.cacheRegistrySpec() + if err != nil { + return nil, err + } + + var registrySpecBytes []byte + registrySpecBytes, err = registrySpec.Marshal() + if err != nil { + return nil, err + } + + // NOTE: We call mkdir after getting the registry spec, since a + // network call might fail and leave this half-initialized empty + // directory. + registrySpecDir := filepath.Join(root(gh.app), gh.RegistrySpecDir()) + err = gh.app.Fs().MkdirAll(registrySpecDir, app.DefaultFolderPermissions) + if err != nil { + return nil, err + } + + err = afero.WriteFile(gh.app.Fs(), registrySpecFile, registrySpecBytes, app.DefaultFilePermissions) + if err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + return registrySpec, nil +} + +func (gh *GitHub) cacheRegistrySpec() (*Spec, error) { ctx := context.Background() file, _, err := gh.ghClient.Contents(ctx, gh.hd.Repo(), gh.hd.regSpecRepoPath, diff --git a/pkg/registry/github_test.go b/pkg/registry/github_test.go index 0701c0d771c8532e87135c9cd1c0ed8190bad342..f692a762a9b13cee803879006bbca9d59fac8bb9 100644 --- a/pkg/registry/github_test.go +++ b/pkg/registry/github_test.go @@ -24,15 +24,23 @@ import ( "github.com/google/go-github/github" "github.com/ksonnet/ksonnet/metadata/app" + amocks "github.com/ksonnet/ksonnet/metadata/app/mocks" "github.com/ksonnet/ksonnet/pkg/parts" ghutil "github.com/ksonnet/ksonnet/pkg/util/github" "github.com/ksonnet/ksonnet/pkg/util/github/mocks" + "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func makeGh(t *testing.T, u, sha1 string) (*GitHub, *mocks.GitHub) { + fs := afero.NewMemMapFs() + appMock := &amocks.App{} + appMock.On("Fs").Return(fs) + appMock.On("Root").Return("/app") + appMock.On("LibPath", mock.AnythingOfType("string")).Return(filepath.Join("/app", "lib", "v1.8.7"), nil) + ghMock := &mocks.GitHub{} ghMock.On("CommitSHA1", mock.Anything, ghutil.Repo{Org: "ksonnet", Repo: "parts"}, "master"). Return(sha1, nil) @@ -45,7 +53,7 @@ func makeGh(t *testing.T, u, sha1 string) (*GitHub, *mocks.GitHub) { URI: "github.com/ksonnet/parts/tree/master/incubator", } - g, err := NewGitHub(spec, optGh) + g, err := NewGitHub(appMock, spec, optGh) require.NoError(t, err) return g, ghMock diff --git a/pkg/registry/manager.go b/pkg/registry/manager.go index 104e50a44e171610d49ed5b71ef287a04b11225c..278c70085ea64113f5ca237032d6913bd42891ba 100644 --- a/pkg/registry/manager.go +++ b/pkg/registry/manager.go @@ -57,7 +57,7 @@ func Package(a app.App, name string) (*pkg.Package, error) { func Locate(a app.App, spec *app.RegistryRefSpec) (Registry, error) { switch spec.Protocol { case ProtocolGitHub: - return githubFactory(spec) + return githubFactory(a, spec) case ProtocolFilesystem: return NewFs(a, spec) default: diff --git a/pkg/registry/manager_test.go b/pkg/registry/manager_test.go index 8e51ec36919d3e623c33acefdf565e339a06a18e..e0741044fbe3544ad0f3316f69e1f344bb41ccec 100644 --- a/pkg/registry/manager_test.go +++ b/pkg/registry/manager_test.go @@ -38,8 +38,8 @@ func Test_Package(t *testing.T) { Return(content, nil, nil) ghcOpt := GitHubClient(c) - githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) { - return NewGitHub(spec, ghcOpt) + githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) { + return NewGitHub(a, spec, ghcOpt) } registries := app.RegistryRefSpecs{ @@ -66,8 +66,8 @@ func Test_List(t *testing.T) { Return("12345", nil) ghcOpt := GitHubClient(c) - githubFactory = func(spec *app.RegistryRefSpec) (*GitHub, error) { - return NewGitHub(spec, ghcOpt) + githubFactory = func(a app.App, spec *app.RegistryRefSpec) (*GitHub, error) { + return NewGitHub(a, spec, ghcOpt) } specs := app.RegistryRefSpecs{ diff --git a/pkg/registry/testdata/incubator/README.md b/pkg/registry/testdata/incubator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..73e0907eccf3e1794bcad8a0433d62c409268d12 --- /dev/null +++ b/pkg/registry/testdata/incubator/README.md @@ -0,0 +1,33 @@ +# Incubator Registry + +## Overview + +This directory is an official ksonnet-compatible [registry][2]. If you are unfamiliar with ksonnet, we recommend browsing [the official site][1] to gain more context. + +Out of the box, ksonnet's CLI tool (`ks`) is aware of the `incubator` registry, and can download any of its libraries via `ks pkg install`. + +## Usage + +Assuming that you have the `ks` tool [installed][3], you can use any library in this registry as follows, by replacing `redis` with `<library-name>`: + +``` +# List all available packages (e.g. apache, efk, mariadb..) +ks pkg list + +# Describe a specific package +ks pkg describe incubator/redis@master + +# Download a specific package +ks pkg install incubator/redis@master +``` + +## Library-specific Documentation + +Each of the libraries in this directory has its own README.md. These are autogenerated from the metadata in their `parts.yaml` file, using the [`doc-gen` script][4]. + +Note that you can use the `ks` commands in your terminal to access this same documentation. + +[1]: https://ksonnet.io +[2]: https://ksonnet.io/docs/concepts#registry +[3]: https://ksonnet.io/#get-started +[4]: /doc-gen/main.go diff --git a/pkg/registry/testdata/incubator/apache/README.md b/pkg/registry/testdata/incubator/apache/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0efbda17d7f6fb7c77a46afa2471dbe6bf780829 --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/README.md @@ -0,0 +1,60 @@ +# apache + +> Apache Ksonnet mixin library contains a simple prototype with pre-configured components to help you deploy a Apache HTTP Server app to a Kubernetes cluster with ease. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.apache-simple](#io.ksonnet.pkg.apache-simple) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.apache-simple` prototype to generate Kubernetes YAML for apache, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.apache-simple apache \ + --name apache \ + --namespace default + +# Apply to server. +$ ks apply -f apache.jsonnet +``` + +## Using the library + +The library files for apache define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure apache for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of apache, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.apache-simple + +Apache HTTP Server. Apache is deployed using a deployment, and exposed to the network using a service. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.apache-simple apache \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace to divvy up your cluster; default is 'default' [string] +* `--name=<name>`: Name to identify all Kubernetes objects in this prototype [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/apache/apache.libsonnet b/pkg/registry/testdata/incubator/apache/apache.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..09e246529b2db7c7af13bb97339842af8086ec4e --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/apache.libsonnet @@ -0,0 +1,107 @@ +local k = import "k.libsonnet"; +local deployment = k.extensions.v1beta1.deployment; + +{ + parts::{ + svc(namespace, name, selector={app: name}):: { + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + spec: { + type: "LoadBalancer", + ports: [ + { + name: "http", + port: 80, + targetPort: "http", + }, + { + name: "https", + port: 443, + targetPort: "https", + }, + ], + selector: selector + }, + }, + + deployment(namespace, name, labels={app: name})::{ + local defaults = { + // ref: https://hub.docker.com/r/bitnami/apache/tags/ + imageTag:: "2.4.23-r12", + // ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + imagePullPolicy:: "IfNotPresent", + }, + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + namespace: namespace, + name: name, + labels: labels + }, + spec: { + replicas: 1, + template: { + metadata: { + labels: labels + }, + spec: { + containers: [ + { + name: name, + image: "bitnami/apache:%s" % defaults.imageTag, + imagePullPolicy: defaults.imagePullPolicy, + ports: [ + { + name: "http", + containerPort: 80, + }, + { + name: "https", + containerPort: 443, + } + ], + livenessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 5, + timeoutSeconds: 3, + periodSeconds: 5, + }, + volumeMounts: [ + { + name: "apache-data", + mountPath: "/bitnami/apache", + } + ], + } + ], + volumes: [ + { + name: "apache-data", + emptyDir: {}, + } + ] + }, + }, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet b/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..523d9d0c9ff05486217bb3b67b579732131a00b2 --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/examples/apache.jsonnet @@ -0,0 +1,13 @@ +local k = import "k.libsonnet"; +local apache = import "../apache.libsonnet"; + + +local namespace = "default"; +local name = "apache-app"; + +k.core.v1.list.new( + [ + apache.parts.deployment(namespace, name), + apache.parts.svc(namespace, name) + ] +) diff --git a/pkg/registry/testdata/incubator/apache/examples/generated.yaml b/pkg/registry/testdata/incubator/apache/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f92073ce7d512387fe5d88eb79aa7e367bba8f77 --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/examples/generated.yaml @@ -0,0 +1,61 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: apache-app + name: apache-app +spec: + replicas: 1 + template: + metadata: + labels: + app: apache-app + spec: + containers: + - image: bitnami/apache:2.4.23-r12 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: / + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: apache-app + ports: + - containerPort: 80 + name: http + - containerPort: 443 + name: https + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - mountPath: /bitnami/apache + name: apache-data + volumes: + - emptyDir: {} + name: apache-data +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: apache-app + name: apache-app +spec: + ports: + - name: http + port: 80 + targetPort: http + - name: https + port: "443" + targetPort: https + selector: + app: apache-app + type: LoadBalancer \ No newline at end of file diff --git a/pkg/registry/testdata/incubator/apache/parts.yaml b/pkg/registry/testdata/incubator/apache/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4ec21a77179498392897e6a8dbda21f95f71695d --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/parts.yaml @@ -0,0 +1,40 @@ +{ + "name": "apache", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Apache Ksonnet mixin library contains a simple prototype with pre-configured components to help you deploy a Apache HTTP Server app to a Kubernetes cluster with ease.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "apache", + "server", + "http" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.apache-simple", + "componentName": "apache", + "flags": { + "name": "apache", + "namespace": "default" + }, + "comment": "Run a simple Apache server" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet b/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..b3fce3b8e8066854780410e1b5005dd7a17324fd --- /dev/null +++ b/pkg/registry/testdata/incubator/apache/prototypes/apache-simple.jsonnet @@ -0,0 +1,18 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.apache-simple +// @description Apache HTTP Server. Apache is deployed using a deployment, and exposed to the +// network using a service. +// @shortDescription A simple, stateless Apache HTTP server. +// @param namespace string Namespace to divvy up your cluster; default is 'default' +// @param name string Name to identify all Kubernetes objects in this prototype + +local k = import 'k.libsonnet'; +local apache = import 'incubator/apache/apache.libsonnet'; + +local namespace = import 'param://namespace'; +local appName = import 'param://name'; + +k.core.v1.list.new([ + apache.parts.deployment(namespace, appName), + apache.parts.svc(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/efk/efk.libsonnet b/pkg/registry/testdata/incubator/efk/efk.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..0acd5db45bcc7d8b2cab09d84d9d2d5361263059 --- /dev/null +++ b/pkg/registry/testdata/incubator/efk/efk.libsonnet @@ -0,0 +1,313 @@ +local k = import 'k.libsonnet'; +local deployment = k.extensions.v1beta1.deployment; +local container = deployment.mixin.spec.template.spec.containersType; +local volume = deployment.mixin.spec.template.spec.volumesType; + +// TODO: Very clearly WIP, needs to be refactored +{ + parts:: { + kibana::{ + deployment(namespace)::{ + "apiVersion":"apps/v1beta1", + "kind":"Deployment", + "metadata":{ + "name":"kibana-logging", + "labels":{ + "k8s-app":"kibana-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile" + } + }, + "spec":{ + "replicas":1, + "selector":{ + "matchLabels":{ + "k8s-app":"kibana-logging" + } + }, + "template":{ + "metadata":{ + "labels":{ + "k8s-app":"kibana-logging" + } + }, + "spec":{ + "containers":[ + { + "name":"kibana-logging", + "image":"docker.elastic.co/kibana/kibana:5.6.2", + "resources":{ + "limits":{ + "cpu":"1000m" + }, + "requests":{ + "cpu":"100m" + } + }, + "env":[ + { + "name":"ELASTICSEARCH_URL", + "value":"http://elasticsearch-logging:9200" + }, + { + "name":"SERVER_BASEPATH", + "value":"/api/v1/proxy/namespaces/" + namespace + "/services/kibana-logging" + }, + { + "name":"XPACK_MONITORING_ENABLED", + "value":"false" + }, + { + "name":"XPACK_SECURITY_ENABLED", + "value":"false" + } + ], + "ports":[ + { + "containerPort":5601, + "name":"ui", + "protocol":"TCP" + } + ] + } + ] + } + } + } + }, + svc::{ + "apiVersion":"v1", + "kind":"Service", + "metadata":{ + "name":"kibana-logging", + "labels":{ + "k8s-app":"kibana-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile", + "kubernetes.io/name":"Kibana" + } + }, + "spec":{ + "ports":[ + { + "port":5601, + "protocol":"TCP", + "targetPort":"ui" + } + ], + "selector":{ + "k8s-app":"kibana-logging" + } + } + }, + + }, + elasticsearch::{ + serviceAccount::{ + "apiVersion":"v1", + "kind":"ServiceAccount", + "metadata":{ + "name":"elasticsearch-logging", + "labels":{ + "k8s-app":"elasticsearch-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile" + } + } + }, + clusterRole::{ + "kind":"ClusterRole", + "apiVersion":"rbac.authorization.k8s.io/v1beta1", + "metadata":{ + "name":"elasticsearch-logging", + "labels":{ + "k8s-app":"elasticsearch-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile" + } + }, + "rules":[ + { + "apiGroups":[ + "" + ], + "resources":[ + "services", + "namespaces", + "endpoints" + ], + "verbs":[ + "get" + ] + } + ] + }, + clusterRoleBinding(namespace)::{ + "kind":"ClusterRoleBinding", + "apiVersion":"rbac.authorization.k8s.io/v1beta1", + "metadata":{ + "name":"elasticsearch-logging", + "namespace": namespace, + "labels":{ + "k8s-app":"elasticsearch-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile" + } + }, + "subjects":[ + { + "kind":"ServiceAccount", + "name":"elasticsearch-logging", + "apiGroup":"" + } + ], + "roleRef":{ + "kind":"ClusterRole", + "name":"elasticsearch-logging", + "apiGroup":"" + } + }, + statefulSet::{ + "apiVersion":"apps/v1beta1", + "kind":"StatefulSet", + "metadata":{ + "name":"elasticsearch-logging", + "labels":{ + "k8s-app":"elasticsearch-logging", + "version":"v5.6.2", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile" + } + }, + "spec":{ + "serviceName":"elasticsearch-logging", + "replicas":2, + "selector":{ + "matchLabels":{ + "k8s-app":"elasticsearch-logging", + "version":"v5.6.2" + } + }, + "template":{ + "metadata":{ + "labels":{ + "k8s-app":"elasticsearch-logging", + "version":"v5.6.2", + "kubernetes.io/cluster-service":"true" + } + }, + "spec":{ + "serviceAccountName":"elasticsearch-logging", + "containers":[ + { + "image":"gcr.io/google-containers/elasticsearch:v5.6.2", + "name":"elasticsearch-logging", + "resources":{ + "limits":{ + "cpu":"1000m" + }, + "requests":{ + "cpu":"100m" + } + }, + "ports":[ + { + "containerPort":9200, + "name":"db", + "protocol":"TCP" + }, + { + "containerPort":9300, + "name":"transport", + "protocol":"TCP" + } + ], + "volumeMounts":[ + { + "name":"elasticsearch-logging", + "mountPath":"/data" + } + ], + "env":[ + { + "name":"NAMESPACE", + "valueFrom":{ + "fieldRef":{ + "fieldPath":"metadata.namespace" + } + } + } + ] + } + ], + "volumes":[ + { + "name":"elasticsearch-logging", + "emptyDir":{ + + } + } + ], + "initContainers":[ + { + "image":"alpine:3.6", + "command":[ + "/sbin/sysctl", + "-w", + "vm.max_map_count=262144" + ], + "name":"elasticsearch-logging-init", + "securityContext":{ + "privileged":true + } + } + ] + } + } + } + }, + svc::{ + "apiVersion":"v1", + "kind":"Service", + "metadata":{ + "name":"elasticsearch-logging", + "labels":{ + "k8s-app":"elasticsearch-logging", + "kubernetes.io/cluster-service":"true", + "addonmanager.kubernetes.io/mode":"Reconcile", + "kubernetes.io/name":"Elasticsearch" + } + }, + "spec":{ + "ports":[ + { + "port":9200, + "protocol":"TCP", + "targetPort":"db" + } + ], + "selector":{ + "k8s-app":"elasticsearch-logging" + } + } + }, + }, + fluentd:: { + // TODO: Add daemonset + sidecar(containerName):: + local volumeName = "logs"; + + deployment.mapContainersWithName( + [containerName], + function(c) c + container.withVolumeMounts(container.volumeMountsType.new(volumeName, "/var/log")) + ) + deployment.mixin.spec.template.spec.withContainers( + container + .new("fluentd-sidecar", "alpinejay/fluentd-sidecar-es:v1.1") + .withEnv(container.envType.new("FILES_TO_COLLECT", "/mnt/log/apache2/access.log /mnt/log/apache2/error.log")) + .withVolumeMounts(container.volumeMountsType.new(volumeName, "/mnt/log", true)) + ) + deployment.mixin.spec.template.spec.withVolumes( + volume.fromEmptyDir(volumeName) + ), + }, + }, +} diff --git a/pkg/registry/testdata/incubator/efk/parts.yaml b/pkg/registry/testdata/incubator/efk/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0be6e0ea672aa02e33175626b124c247297a79fd --- /dev/null +++ b/pkg/registry/testdata/incubator/efk/parts.yaml @@ -0,0 +1,31 @@ +name: efk +apiVersion: 0.0.1 +kind: ksonnet.io/parts +description: > + EFK (elasticsearch-fluentd-kibana) is a common logging stack used with + kubernetes. +author: ksonnet team <ksonnet-help@heptio.com> +contributors: +- name: Tehut Getahun + email: tehut@heptio.com +- name: Tamiko Terada + email: tamiko@heptio.com +repository: + type: git + url: https://github.com/ksonnet/mixins +bugs: + url: https://github.com/ksonnet/mixins/issues +keywords: +- elasticsearch +- fluentd +- kibana +- logging +quickStart: + prototype: io.ksonnet.pkg.elasticsearch-kibana + componentName: elasticsearch-kibana + flags: + name: elasticsearch-kibana + namespace: default + password: boots + comment: Logging stack that processes input from fluentd. +license: Apache 2.0 diff --git a/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet b/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..3600ff20890758f92511c3079cd8e85f4e1f5398 --- /dev/null +++ b/pkg/registry/testdata/incubator/efk/prototypes/elasticsearch-kibana.jsonnet @@ -0,0 +1,21 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.elasticsearch-kibana +// @description Elasticsearch and Kibana stack for logging. Elasticsearch +// indexes the logs, and kibana provides a queryable, interactive UI. +// @shortDescription The Elasticsearch and Kibana setup for an EFK logging stack. +// @optionalParam namespace string default Namespace in which to put the application + +local k = import 'k.libsonnet'; +local efk = import 'incubator/efk/efk.libsonnet'; + +local namespace = import 'param://namespace'; + +k.core.v1.list.new([ + efk.parts.kibana.deployment(namespace), + efk.parts.kibana.svc, + efk.parts.elasticsearch.serviceAccount, + efk.parts.elasticsearch.clusterRole, + efk.parts.elasticsearch.clusterRoleBinding(namespace), + efk.parts.elasticsearch.statefulSet, + efk.parts.elasticsearch.svc, +]) diff --git a/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet b/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..19e0a2703cceba40ee90a02a503a673d7abf8723 --- /dev/null +++ b/pkg/registry/testdata/incubator/efk/prototypes/fluentd-es-sidecar.jsonnet @@ -0,0 +1,13 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.fluentd-es-sidecar +// @description A fluentd sidecar that can be added to a container to scrape +// and preprocess logs for elasticsearch. +// @shortDescription The fluentd sidecar to scrape logs for an EFK stack +// @param containerName string Name of the main container to be logged + +local k = import 'k.libsonnet'; +local efk = import 'incubator/efk/efk.libsonnet'; + +local containerName = import 'param://containerName'; + +efk.parts.fluentd.sidecar(containerName) diff --git a/pkg/registry/testdata/incubator/mariadb/README.md b/pkg/registry/testdata/incubator/mariadb/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a0ef8fc30fca51304ee8c401654f3bd898eae49d --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/README.md @@ -0,0 +1,71 @@ +# mariadb + +> MariaDB is an open source relational database it provides a SQL interface for accessing data. The latest versions of MariaDB also include GIS and JSON features. This package deploys a maria container, a service and secret to your cluster + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.stateless-maria](#io.ksonnet.pkg.stateless-maria) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.simple-mariadb` prototype to generate Kubernetes YAML for mariadb, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.simple-mariadb mariadb \ + --name mariadb \ + --namespace default \ + --mariaRootPassword boot + +# Apply to server. +$ ks apply -f mariadb.jsonnet +``` + +## Using the library + +The library files for mariadb define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mariadb for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of mariadb, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.stateless-maria + +Deploy stateless instance of MariaDB. This is NOT backed by a persistent volume.The MariaDB container is deployed using a deployment and exposed to the +network as a service. The password is stored as a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.stateless-maria mariadb \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE \ + --mariaRootPassword YOUR_MARIAROOTPASSWORD_HERE +``` + +Below is the Jsonnet file generated by this command. + +``` +// mariadb.jsonnet +<JSONNET HERE> +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Metadata name for each of the deployment components [string] +* `--mariaRootPassword=<mariaRootPassword>`: Password for root user [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml b/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8575dee77b9bdc6f9aa1d0843ecd73fa763100af --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/examples/generated.yaml @@ -0,0 +1,146 @@ +--- +apiVersion: v1 +data: + my.cnf: | + [mysqld] + innodb_buffer_pool_size=2G +kind: ConfigMap +metadata: + labels: + app: mariadb-app + name: mariadb-app +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: mariadb-app + name: mariadb-app +spec: + template: + metadata: + labels: + app: mariadb-app + spec: + containers: + - env: + - name: MARIADB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + key: mariadb-root-password + name: mariadb-app + - name: MARIADB_USER + value: "" + - name: MARIADB_DATABASE + value: "" + image: bitnami/mariadb:10.1.26-r2 + imagePullPolicy: IfNotPresent + livenessProbe: + exec: + command: + - mysqladmin + - ping + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: mariadb + ports: + - containerPort: 3306 + name: mysql + readinessProbe: + exec: + command: + - mysqladmin + - ping + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + requests: + cpu: 250m + memory: 256Mi + volumeMounts: + - mountPath: /bitnami/mariadb/conf/my_custom.cnf + name: config + subPath: my.cnf + - mountPath: /bitnami/mariadb + name: data + - command: + - sh + - -c + - DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter + env: + - name: MARIADB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + key: mariadb-root-password + name: mariadb-app + image: prom/mysqld-exporter:v0.10.0 + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: metrics + initialDelaySeconds: 15 + timeoutSeconds: 5 + name: metrics + ports: + - containerPort: 9104 + name: metrics + readinessProbe: + httpGet: + path: /metrics + port: metrics + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: {} + volumes: + - configMap: + name: mariadb-app + name: config + - name: data + persistentVolumeClaim: + claimName: mariadb-app +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app: mariadb-app + name: mariadb-app +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: v1 +data: + mariadb-password: Ym9vdHM= + mariadb-root-password: YWxzb2Jvb3Rz +kind: Secret +metadata: + labels: + app: mariadb-app + name: mariadb-app +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + prometheus.io/port: "9104" + prometheus.io/scrape: "true" + labels: + app: mariadb-app + name: mariadb-app +spec: + ports: + - name: mysql + port: 3306 + targetPort: mysql + - name: metrics + port: 9104 + targetPort: metrics + selector: + app: mariadb-app + type: ClusterIP \ No newline at end of file diff --git a/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet b/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..c98e7e9b43a9dfd724a5beec7a18e14304138dc9 --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/examples/maria.jsonnet @@ -0,0 +1,10 @@ +local k = import 'k.libsonnet'; +local maria = import '../maria.libsonnet'; + +k.core.v1.list.new([ + maria.parts.configMap("dev-hoot", "mariadb-app"), + maria.parts.deployment.persistent("dev-hoot", "mariadb-app", "passwordSecret"), + maria.parts.pvc("dev-hoot", "mariadb-app"), + maria.parts.secret("dev-hoot", "mariadb-app", "mariaRootPassword", "mariadbPassword"), + maria.parts.svc("dev-hoot", "mariadb-app") +]) diff --git a/pkg/registry/testdata/incubator/mariadb/maria.libsonnet b/pkg/registry/testdata/incubator/mariadb/maria.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..6447bd78c3a9798665a20149b3183cd46f92a02e --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/maria.libsonnet @@ -0,0 +1,307 @@ +local k = import 'k.libsonnet'; +local deployment = k.extensions.v1beta1.deployment; + +{ + parts:: { + svc(namespace, name, metricsEnabled=true, labels={app:name}, selector={app:name}):: + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + labels: labels, + [if metricsEnabled then "annotations"]: { + "prometheus.io/scrape": "true", + "prometheus.io/port": "9104", + } + }, + spec: { + type: "ClusterIP", + ports: [ + { + name: "mysql", + port: 3306, + targetPort: "mysql", + }, + ] + if metricsEnabled then [ + { + name: "metrics", + port: 9104, + targetPort: "metrics", + }, + ] else [], + selector: selector, + }, + }, + + secret(namespace, name, mariaRootPassword, labels={app:name},):: { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + type: "Opaque", + data: { + "mariadb-root-password": std.base64(mariaRootPassword), + }, + }, + + configMap(namespace, name, labels={app:name}):: + local config = ||| + [mysqld] + innodb_buffer_pool_size=2G + |||; + { + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + data: { + "my.cnf": config, + }, + }, + + pvc(namespace, name, storageClassName="-", labels={app:name}):: + local defaults = { + accessMode: "ReadWriteOnce", + size: "8Gi" + }; + { + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + spec: { + accessModes: [ + defaults.accessMode, + ], + resources: { + requests: { + storage: defaults.size, + }, + }, + [if storageClassName != null then "storageClass"]:storageClassName, + }, + }, + + deployment:: { + local defaults = { + image: "bitnami/mariadb:10.1.26-r2", + imagePullPolicy: "IfNotPresent", + serviceType: "ClusterIP", + persistence: { + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + resources: { + requests: { + memory: "256Mi", + cpu: "250m", + }, + }, + metrics: { + image: "prom/mysqld-exporter", + imageTag: "v0.10.0", + imagePullPolicy: "IfNotPresent", + resources: {}, + annotations: { + "prometheus.io/scrape": "true", + "prometheus.io/port": "9104", + }, + }, + mariaConfig: { + user: "", + db: "", + }, + }, + + persistent(namespace, name, passwordSecretName, mariaConfig=defaults.mariaConfig, metricsEnabled=true, existingClaim=name, labels={app:name}, configMapName=name):: + local volume = { + name: "data", + persistentVolumeClaim: { + claimName: existingClaim + } + }; + base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName) + + deployment.mixin.spec.template.spec.withVolumes(volume), + + nonPersistent(namespace, name, passwordSecretName, mariaConfig=defaults.mariaConfig, metricsEnabled=true, existingClaim=name, labels={app:name}, configMapName=name):: + base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName), + + local secure(passwordSecretName) = [ + { + name: "MARIADB_ROOT_PASSWORD", + valueFrom: { + secretKeyRef: { + name: passwordSecretName, + key: "mariadb-root-password", + }, + }, + }, + { + name: "MARIADB_PASSWORD", + valueFrom: { + secretKeyRef: { + name: passwordSecretName, + key: "mariadb-password", + }, + }, + }, + ], + + local insecure(passwordSecretName) = [ + { + name: "ALLOW_EMPTY_PASSWORD", + value: "yes", + }, + { + name: "MARIADB_PASSWORD", + valueFrom: { + secretKeyRef: { + name: passwordSecretName, + key: "mariadb-password", + }, + }, + } + ], + + local base(namespace, name, passwordSecretName, mariaConfig, metricsEnabled, existingClaim, labels, configMapName) = + local metricsContainer = + if !metricsEnabled then [] + else [ + { + name: "metrics", + image: "%s:%s" % [defaults.metrics.image, defaults.metrics.imageTag], + imagePullPolicy: defaults.metrics.imagePullPolicy, + env: [ + { + name: "MARIADB_ROOT_PASSWORD", + valueFrom: { + secretKeyRef: { + name: name, + key: "mariadb-root-password", + }, + }, + }, + ], + command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ], + ports: [ + { + name: "metrics", + containerPort: 9104, + }, + ], + livenessProbe: { + httpGet: { + path: "/metrics", + port: "metrics", + }, + initialDelaySeconds: 15, + timeoutSeconds: 5, + }, + readinessProbe: { + httpGet: { + path: "/metrics", + port: "metrics", + }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + resources: defaults.metrics.resources, + }, + ]; + + { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + spec: { + template: { + metadata: { + namespace: namespace, + labels: labels, + }, + spec: { + containers: [ + { + name: "mariadb", + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + env: + secure(passwordSecretName) + [ + { + name: "MARIADB_USER", + value: mariaConfig.user + }, + { + name: "MARIADB_DATABASE", + value: mariaConfig.db + }, + ], + ports: [ + { + name: "mysql", + containerPort: 3306, + }, + ], + livenessProbe: { + exec: { + command: [ + "mysqladmin", + "ping", + ], + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + }, + readinessProbe: { + exec: { + command: [ + "mysqladmin", + "ping", + ], + }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + resources: defaults.resources, + volumeMounts: [ + { + name: "config", + mountPath: "/bitnami/mariadb/conf/my_custom.cnf", + subPath: "my.cnf", + }, + { + name: "data", + mountPath: "/bitnami/mariadb", + }, + ], + }, + ] + metricsContainer, + volumes: [ + { + name: "config", + configMap: { + name: configMapName, + }, + } + ], + }, + }, + }, + }, + }, + } +} diff --git a/pkg/registry/testdata/incubator/mariadb/parts.yaml b/pkg/registry/testdata/incubator/mariadb/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d17c772d4451d69cedc1195db91879ef14a7d5c8 --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/parts.yaml @@ -0,0 +1,41 @@ +{ + "name": "mariadb", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "MariaDB is an open source relational database it provides a SQL interface for accessing data. The latest versions of MariaDB also include GIS and JSON features. This package deploys a maria container, a service and secret to your cluster", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "mariadb", + "database", + "relational" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.simple-mariadb", + "componentName": "mariadb", + "flags": { + "name": "mariadb", + "namespace": "default", + "mariaRootPassword": "boot" + }, + "comment": "Run a simple mariadb database" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet b/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..4ce9ea3eaf4faff05b34712ca8bc8e6e0d11e7af --- /dev/null +++ b/pkg/registry/testdata/incubator/mariadb/prototypes/maria-stateless.jsonnet @@ -0,0 +1,22 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.stateless-maria +// @description Deploy stateless instance of MariaDB. This is NOT backed by a persistent volume. +// The MariaDB container is deployed using a deployment and exposed to the +// network as a service. The password is stored as a secret. +// @shortDescription A simple, stateless MariaDB deployment. +// @param namespace string Namespace in which to put the application +// @param name string Metadata name for each of the deployment components +// @param mariaRootPassword string Password for root user + +local k = import 'k.libsonnet'; +local maria = import 'incubator/mariadb/maria.libsonnet'; + +local namespace = import 'param://namespace'; +local name = import 'param://name'; +local mariaRootPassword = import 'param://mariaRootPassword'; + +k.core.v1.list.new([ + maria.parts.deployment.nonPersistent(namespace, name, name), + maria.parts.secret(namespace, name, mariaRootPassword), + maria.parts.svc(namespace, name) + ]) diff --git a/pkg/registry/testdata/incubator/memcached/README.md b/pkg/registry/testdata/incubator/memcached/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e9f60473f4090923e0584b4dfdeb30c23e8b3c82 --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/README.md @@ -0,0 +1,68 @@ +# memcached + +> Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.memcached-simple](#io.ksonnet.pkg.memcached-simple) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.memcached-simple` prototype to generate Kubernetes YAML for memcached, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.memcached-simple memcached \ + --name memcached \ + --namespace default + +# Apply to server. +$ ks apply -f memcached.jsonnet +``` + +## Using the library + +The library files for memcached define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure memcached for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of memcached, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.memcached-simple + +Deploys Memcached on a your Kubernetes cluster through a stateful set with 3replicas, pod distribution budget (pdb), and service. Memcached +can be accessed via port 11211 within the cluster. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.memcached-simple memcached \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE +``` + +Below is the Jsonnet file generated by this command. + +``` +// memcached.jsonnet +<JSONNET HERE> +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Name to give to each of the components [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/memcached/examples/generated.yaml b/pkg/registry/testdata/incubator/memcached/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..304dd9f8167ce21e7f93698401ccbf4a2901599d --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/examples/generated.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: memcached + namespace: dev-alex +spec: + minAvailable: 3 + selector: + matchLabels: + app: memcached +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + labels: + app: memcached + name: memcached + namespace: dev-alex +spec: + replicas: 3 + serviceName: memcached + template: + metadata: + labels: + app: memcached + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: memcached + topologyKey: kubernetes.io/hostname + containers: + - command: + - memcached + - -m 64 + - -o + - modern + image: memcached:1.4.36-alpine + imagePullPolicy: IfNotPresent + livenessProbe: + initialDelaySeconds: 30 + tcpSocket: + port: memcache + timeoutSeconds: 5 + name: memcached + ports: + - containerPort: 11211 + name: memcache + readinessProbe: + initialDelaySeconds: 5 + tcpSocket: + port: memcache + timeoutSeconds: 1 + resources: + requests: + cpu: 50m + memory: 64Mi +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: memcached + name: memcached + namespace: dev-alex +spec: + clusterIP: None + ports: + - name: memcache + port: 11211 + targetPort: memcache + selector: + app: memcached diff --git a/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet b/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..20455975e89ef165f698ec0f720bf2462af589bd --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/examples/memcached.jsonnet @@ -0,0 +1,11 @@ +local k = import 'k.libsonnet'; +local memcached = import '../memcached.libsonnet'; + +local myNamespace = "dev-alex"; +local appName = "memcached"; + +k.core.v1.list.new([ + memcached.parts.pbd(myNamespace, appName), + memcached.parts.statefulset.withHardAntiAffinity(myNamespace, appName), + memcached.parts.service(myNamespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/memcached/memcached.libsonnet b/pkg/registry/testdata/incubator/memcached/memcached.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..c0e93815649c8f3bed349c829a581b61d2e28dd9 --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/memcached.libsonnet @@ -0,0 +1,149 @@ +local k = import 'k.libsonnet'; + +{ + parts:: { + pdb(namespace, name, pdbMinAvailable=3, selector={matchLabels: {app: name}}):: { + apiVersion: "policy/v1beta1", + kind: "PodDisruptionBudget", + metadata: { + namespace: namespace, + name: name, + }, + spec: { + selector: selector, + minAvailable: pdbMinAvailable, + }, + }, + + statefulset:: { + local defaults = { + replicaCount: 3, + resources: { + requests: { + memory: "64Mi", + cpu: "50m", + }, + }, + image: "memcached:1.4.36-alpine", + imagePullPolicy: "IfNotPresent", + // imagePullPolicy: change to "Always" if the imageTag is "latest" + memcached: { + maxItemMemory: 64, + verbosity: "v", + extendedOptions: "modern", + }, + }, + + withHardAntiAffinity(namespace, name, labels={app: name}):: + local hardAntiAffinity = { + requiredDuringSchedulingIgnoredDuringExecution: [ + { + topologyKey: "kubernetes.io/hostname", + labelSelector: { matchLabels: labels }, + }, + ], + }; + base(namespace, name, labels) + + k.apps.v1beta1.statefulSet.mixin.spec.template.spec.affinity.podAntiAffinity.mixinInstance(hardAntiAffinity), + + withSoftAntiAffinity(namespace, name, labels={app: name}):: + local softAntiAffinity = { + preferredDuringSchedulingIgnoredDuringExecution: [ + { + weight: 5, + podAffinityTerm: [ + { + topologyKey: "kubernetes.io/hostname", + labelSelector: { matchLabels: labels }, + }, + ], + }, + ], + }; + base(namespace, name, labels) + + k.apps.v1beta1.statefulSet.mixin.spec.template.spec.affinity.podAntiAffinity.mixinInstance(softAntiAffinity), + + withNoAntiAffinity(namespace, name, labels={app: name}):: + base(namespace, name, labels), + + local base(namespace, name, labels) = { + apiVersion: "apps/v1beta1", + kind: "StatefulSet", + metadata: { + namespace: namespace, + name: name, + labels: labels, + }, + spec: { + serviceName: name, + replicas: defaults.replicaCount, + template: { + metadata: { + labels: labels + }, + spec: { + affinity: { + podAntiAffinity: {} + }, + containers: [ + { + name: name, + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + command: [ + "memcached", + "-m " + defaults.memcached.maxItemMemory + ] + + if "extendedOptions" in defaults.memcached + then [ "-o", defaults.memcached.extendedOptions ] + else [] + + if "verbosity" in defaults.memcached + then [ defaults.memcached.verbosity ] + else [], + ports: [ + { + name: "memcache", + containerPort: 11211, + } + ], + livenessProbe: { + tcpSocket: { port: "memcache" }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + }, + readinessProbe: { + tcpSocket: { port: "memcache" }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + resources: defaults.resources, + }, + ], + }, + }, + }, + }, + }, + + service(namespace, name, selector={app: name}):: { + apiVersion: "v1", + kind: "Service", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + spec: { + clusterIP: "None", + ports: [ + { + name: "memcache", + port: 11211, + targetPort: "memcache" + }, + ], + selector: selector, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/memcached/parts.yaml b/pkg/registry/testdata/incubator/memcached/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..842107024ead0e17688ed0fc254a2cf73ab6fe9f --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/parts.yaml @@ -0,0 +1,40 @@ +{ + "name": "memcached", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "memcached", + "database", + "cache" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.memcached-simple", + "componentName": "memcached", + "flags": { + "name": "memcached", + "namespace": "default" + }, + "comment": "Run a simple memcached instance" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet b/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..cacf7e2431ad749ebd8027a979c08ea78d3b6089 --- /dev/null +++ b/pkg/registry/testdata/incubator/memcached/prototypes/memcached-simple.jsonnet @@ -0,0 +1,22 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.memcached-simple +// @description Deploys Memcached on a your Kubernetes cluster through a stateful set with 3 +// replicas, pod distribution budget (pdb), and service. Memcached +// can be accessed via port 11211 within the cluster. +// @shortDescription Simple Memcached instance with 3 replicas. +// @param namespace string Namespace in which to put the application +// @param name string Name to give to each of the components + +// TODO: Add MaxItemMemory=64 as a param like the k8s/charts? + +local k = import 'k.libsonnet'; +local memcached = import 'incubator/memcached/memcached.libsonnet'; + +local namespace = import 'param://namespace'; +local appName = import 'param://name'; + +k.core.v1.list.new([ + memcached.parts.pdb(namespace, appName), + memcached.parts.statefulset.withHardAntiAffinity(namespace, appName), + memcached.parts.service(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/mongodb/README.md b/pkg/registry/testdata/incubator/mongodb/README.md new file mode 100644 index 0000000000000000000000000000000000000000..75a7b7c62f48e140e5345524f46ead0d9bba7309 --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/README.md @@ -0,0 +1,67 @@ +# mongodb + +> MongoDB is a cross-platform document-oriented database. Classified as a NoSQL database, MongoDB eschews the traditional table-based relational database structure in favor of JSON-like documents with dynamic schemas, making the integration of data in certain types of applications easier and faster. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.mongodb-simple](#io.ksonnet.pkg.mongodb-simple) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.mongodb-simple` prototype to generate Kubernetes YAML for mongodb, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.mongodb-simple mongo \ + --rootPassword boots \ + --password boots \ + --name mongodb \ + --namespace default + +# Apply to server. +$ ks apply -f mongo.jsonnet +``` + +## Using the library + +The library files for mongodb define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mongodb for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of mongodb, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.mongodb-simple + +Deploys a simple instance of mongodb, backed by a persistent volume claim. The mongodb container is deployed using a Kubernetes deployment, and exposed +to the network using a service. Passwords are stored in a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.mongodb-simple mongo \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE \ + --rootPassword YOUR_ROOTPASSWORD_HERE \ + --password YOUR_PASSWORD_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace to specify destination in cluster; default is 'default' [string] +* `--name=<name>`: Name of app to attach as identifier to all components [string] +* `--rootPassword=<rootPassword>`: RootPassword for db admin password [string] +* `--password=<password>`: Password for db user password [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml b/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..17259f17e636afc4ca0708a28b0f7a9d49e90ccb --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/examples/generated.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: mongo + name: mongo +spec: + template: + metadata: + labels: + app: mongo + spec: + containers: + - env: + - name: MONGODB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + key: mongodb-root-password + name: mongo + - name: MONGODB_USERNAME + value: "" + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + key: mongodb-password + name: mongo + - name: MONGODB_DATABASE + value: "" + image: bitnami/mongodb:3.4.7-r0 + livenessProbe: + exec: + command: + - mongo + - --eval + - db.adminCommand('ping') + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: mongo + ports: + - containerPort: 27017 + name: mongodb + readinessProbe: + exec: + command: + - mongo + - --eval + - db.adminCommand('ping') + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /bitnami/mongodb + name: data + volumes: + - name: data + persistentVolumeClaim: + claimName: mongo +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mongo +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: v1 +data: + mongodb-password: YmFy + mongodb-root-password: Zm9vYmFy +kind: Secret +metadata: + labels: + app: mongo + name: mongo +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: mongo + name: mongo +spec: + ports: + - name: mongodb + port: 27017 + targetPort: mongodb + selector: + app: mongo + type: ClusterIP diff --git a/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet b/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..10fe1b1d08c698d757ca8c54386a8453b6f92ae7 --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/examples/mongodb.jsonnet @@ -0,0 +1,15 @@ +local k = import 'k.libsonnet'; +local mongo = import '../mongodb.libsonnet'; + +local namespace = "dev-alex"; +local appName = "mongo"; +local rootPassword = "foobar"; +local password = "bar"; + + +k.core.v1.list.new([ + mongo.parts.deployment.persistent(namespace, appName), + mongo.parts.pvc(namespace, appName), + mongo.parts.secrets(namespace, appName, rootPassword, password), + mongo.parts.service(namespace, appName), +]) diff --git a/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet b/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..7daac00c042a6a876982cbfdf6312d63061156b6 --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/mongodb.libsonnet @@ -0,0 +1,182 @@ +local k = import 'k.libsonnet'; +local deployment = k.extensions.v1beta1.deployment; + +{ + parts:: { + deployment:: { + local defaults = { + image: "bitnami/mongodb:3.4.7-r0", + imagePullPolicy: "IfNotPresent", + resources: { + requests: { + memory: "256Mi", + cpu: "100m", + }, + }, + + persistence: { + enabled: true, + storageClass: "-", + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + + mongoConfig: { + username: "", + database: "", + }, + }, + + persistent(namespace, name, mongoConfig=defaults.mongoConfig, labels={app: name}, pvcName={claimName: name}):: + base(namespace, name, mongoConfig, labels) + .withVolumes({ + name: "data", + persistentVolumeClaim: pvcName, + }), + + nonPersistent(namespace, name, labels={app: name}, mongoConfig=defaults.mongoConfig):: + base(namespace, name, mongoConfig, labels) + .withVolumes({ + name: "data", + emptyDir: {}, + }), + + local base(namespace, name, mongoConfig, labels) = { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + namespace: namespace, + name: name, + labels: labels, + }, + spec: { + template: { + metadata: { labels: labels }, + spec: { + containers: [{ + name: name, + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + env: [ + { + name: "MONGODB_ROOT_PASSWORD", + valueFrom: { + secretKeyRef: { + name: name, + key: "mongodb-root-password", + }, + }, + }, + { + name: "MONGODB_USERNAME", + value: mongoConfig.username, + }, + { + name: "MONGODB_PASSWORD", + valueFrom: { + secretKeyRef: { + name: name, + key: "mongodb-password", + }, + }, + }, + { + name: "MONGODB_DATABASE", + value: mongoConfig.database, + }, + ], + ports: [{ + name: "mongodb", + containerPort: 27017, + }], + livenessProbe: { + exec: { + command: [ + "mongo", + "--eval", + "db.adminCommand('ping')", + ], + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + }, + readinessProbe: { + exec: { + command: [ + "mongo", + "--eval", + "db.adminCommand('ping')", + ], + }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + volumeMounts: [{ + name: "data", + mountPath: "/bitnami/mongodb", + }], + resources: defaults.resources, + }], + }, + }, + }, + }, + }, + + secrets(namespace, name, mongodbRootPassword, mongodbPassword):: { + apiVersion: "v1", + kind: "Secret", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + type: "Opaque", + data: { + "mongodb-root-password": std.base64(mongodbRootPassword), + "mongodb-password": std.base64(mongodbPassword) + }, + }, + + service(namespace, name, serviceType="ClusterIP", selector={app: name}):: { + apiVersion: "v1", + kind: "Service", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + spec: { + type: serviceType, + ports: [{ + name: "mongodb", + port: 27017, + targetPort: "mongodb", + }], + selector: selector, + }, + }, + + pvc(namespace, name, storageClass="-"):: { + local defaults = { + size: "8Gi", + accessMode: "ReadWriteOnce", + enabled: true, + }, + + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + namespace: namespace, + name: name, + }, + spec: { + accessModes: [ defaults.accessMode ], + resources: { + requests: { storage: defaults.size }, + }, + storageClass: storageClass, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/mongodb/parts.yaml b/pkg/registry/testdata/incubator/mongodb/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..16632ad6ecbf678fbbabf1a2078de9c83fc4d30f --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/parts.yaml @@ -0,0 +1,43 @@ +{ + "name": "mongodb", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "MongoDB is a cross-platform document-oriented database. Classified as a NoSQL database, MongoDB eschews the traditional table-based relational database structure in favor of JSON-like documents with dynamic schemas, making the integration of data in certain types of applications easier and faster.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "mongodb", + "database", + "mongo", + "nosql" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.mongodb-simple", + "componentName": "mongo", + "flags": { + "name": "mongodb", + "namespace": "default", + "rootPassword": "boots", + "password": "boots" + }, + "comment": "Run a simple instance of MongoDB" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet b/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..501414893365ae75deb0f2965a48ddf8acedf87e --- /dev/null +++ b/pkg/registry/testdata/incubator/mongodb/prototypes/mongodb-simple.jsonnet @@ -0,0 +1,25 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.mongodb-simple +// @description Deploys a simple instance of mongodb, backed by a persistent volume claim. The +// mongodb container is deployed using a Kubernetes deployment, and exposed +// to the network using a service. Passwords are stored in a secret. +// @shortDescription A simple MongoDB deployment, backed by persistent storage. +// @param namespace string Namespace to specify destination in cluster; default is 'default' +// @param name string Name of app to attach as identifier to all components +// @param rootPassword string RootPassword for db admin password +// @param password string Password for db user password + +local k = import 'k.libsonnet'; +local mongo = import 'incubator/mongodb/mongodb.libsonnet'; + +local namespace = import 'param://namespace/'; +local appName = import 'param://name'; +local rootPassword = import 'param://rootPassword'; +local password = import 'param://password'; + +k.core.v1.list.new([ + mongo.parts.deployment.persistent(namespace, appName), + mongo.parts.pvc(namespace, appName), + mongo.parts.secrets(namespace, appName, rootPassword, password), + mongo.parts.service(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/mysql/README.md b/pkg/registry/testdata/incubator/mysql/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bf300afefab98c9dd1ba38f60a80595a6118fba2 --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/README.md @@ -0,0 +1,65 @@ +# mysql + +> MySQL is one of the most popular database servers in the world. Notable users include Wikipedia, Facebook and Google. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.simple-mysql](#io.ksonnet.pkg.simple-mysql) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.simple-mysql` prototype to generate Kubernetes YAML for mysql, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.simple-mysql mysql \ + --name mysql \ + --namespace default + +# Apply to server. +$ ks apply -f mysql.jsonnet +``` + +## Using the library + +The library files for mysql define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure mysql for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of mysql, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.simple-mysql + +Deploys MySQL backed by a persistent volume. The MySQL container is deployed using a deployment and exposed to the network with a service. The +passwords are stored in a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.simple-mysql mysql \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE \ + --mysqlRootPassword YOUR_MYSQLROOTPASSWORD_HERE \ + --mysqlPassword YOUR_MYSQLPASSWORD_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Name to give to each of the components [string] +* `--mysqlRootPassword=<mysqlRootPassword>`: Password for root user [string] +* `--mysqlPassword=<mysqlPassword>`: Password for new user [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/mysql/examples/generated.yaml b/pkg/registry/testdata/incubator/mysql/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cac246f5dc670ecc4f25d20d32d4b09e2d772794 --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/examples/generated.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: v1 +data: + configurationFiles: + mysql.cnf: | + - [mysqld] + skip-name-resolve +kind: ConfigMap +metadata: + name: mysql + namespace: dev-hoot +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: mysql + name: mysql +spec: + template: + metadata: + labels: + app: mysql + namespace: dev-hoot + spec: + containers: + - env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + key: mysql-root-password + name: mysql + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + key: mysql-password + name: mysql + - name: MYSQL_USER + value: "" + - name: MYSQL_DATABASE + value: "" + image: mysql:5.7.14 + imagePullPolicy: IfNotPresent + livenessProbe: + exec: + command: + - sh + - -c + - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: mysql + ports: + - containerPort: 3306 + name: mysql + readinessProbe: + exec: + command: + - sh + - -c + - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /var/lib/mysql + name: data + - mountPath: /etc/mysql/conf.d + name: configurations + initContainers: + - command: + - rm + - -fr + - /var/lib/mysql/lost+found + image: busybox:1.25.0 + imagePullPolicy: IfNotPresent + name: remove-lost-found + volumeMounts: + - mountPath: /var/lib/mysql + name: data + volumes: + - configMap: + name: mysql + name: configurations +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app: mysql + name: mysql +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: v1 +data: + mysql-password: Zm9v + mysql-root-password: YmFy +kind: Secret +metadata: + labels: + app: mysql + namespace: dev-hoot + name: mysql +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: mysql + name: mysql +spec: + ports: + - name: mysql + port: 3306 + targetPort: mysql + selector: + app: mysql diff --git a/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet b/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..caeb5e98e2c1898dce3c9e68802cbb60ca2009be --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/examples/mysql.jsonnet @@ -0,0 +1,11 @@ +local k = import 'k.libsonnet'; +local mql = import "../mysql.libsonnet"; + + +k.core.v1.list.new([ + mql.parts.configMap("dev-hoot", "mysql"), + mql.parts.deployment.persistent("dev-hoot", "mysql", "mysql", "claimName"), + mql.parts.pvc("dev-hoot", "mysql"), + mql.parts.secret("dev-hoot", "mysql", "foo", "bar"), + mql.parts.svc("dev-hoot", "mysql") +]) diff --git a/pkg/registry/testdata/incubator/mysql/mysql.libsonnet b/pkg/registry/testdata/incubator/mysql/mysql.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..fd59c673864223e2a4ada61646f40526f5e8bca5 --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/mysql.libsonnet @@ -0,0 +1,274 @@ +local k = import 'k.libsonnet'; + +{ + parts:: { + svc(namespace, name, selector= {app:name}):: { + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + spec: { + ports: [ + { + name: "mysql", + port: 3306, + targetPort: "mysql", + }, + ], + selector: selector + }, + }, + + secret(namespace, name, mysqlPassword, mysqlRootPassword):: { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name, + }, + }, + type: "Opaque", + data: { + "mysql-root-password": std.base64(mysqlRootPassword), + "mysql-password": std.base64(mysqlPassword), + }, + }, + + pvc(namespace, name, storageClassName=null):: { + local defaults = { + persistence: { + enabled: true, + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + }, + + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + spec: { + accessModes: [ + defaults.persistence.accessMode, + ], + resources: { + requests: { + storage: defaults.persistence.size, + }, + }, + // I chose this route to avoid passing both storageClass and storageClassName + // it does feel a bit hacky and I'm willing to change it + [if storageClassName != null then "storageClass"]: + storageClassName, + }, + }, + + configMap(namespace,name,configurationFiles= + {configurationFiles: { + "mysql.cnf": + ||| + - [mysqld] + skip-name-resolve + |||, + }}):: { + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + name: name, + namespace: namespace, + }, + data: configurationFiles, + }, + + deployment:: { + local defaults = { + imagePullPolicy: "IfNotPresent", + image: "mysql", + imageTag: "5.7.14", + persistence: { + enabled: true, + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + resources: { + requests: { + memory: "256Mi", + cpu: "100m", + }, + }, + }, + + persistent(namespace, name, secretKeyName, claimName, mysqlUser="", mysqlDatabase="", mysqlAllowEmptyPassword=false, subPath=null, persistenceEnabled=true):: + base(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword, subPath, persistenceEnabled), + + nonpersistent(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword=false, subPath=null, persistenceEnabled=false):: + base(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword, subPath, persistenceEnabled), + + local base(namespace, name, secretKeyName, claimName, mysqlUser, mysqlDatabase, mysqlAllowEmptyPassword, subPath, persistenceEnabled)= { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + name: name, + labels: { + app: name, + }, + }, + spec: { + template: { + metadata: { + labels: { + app: name, + namespace: namespace + }, + }, + spec: { + initContainers: [ + { + name: "remove-lost-found", + image: "busybox:1.25.0", + // imagePullPolicy: change to "Always" if the imageTag is "latest" + imagePullPolicy: defaults.imagePullPolicy, + command: ["rm", "-fr", "/var/lib/mysql/lost+found"], + volumeMounts: [ + { + name: "data", + mountPath: "/var/lib/mysql", + [if subPath != null then "subPath"]: subPath + }, + ] + }, + ], + containers: [ + { + name: name, + image: "%s:%s" % [defaults.image, defaults.imageTag], + imagePullPolicy: defaults.imagePullPolicy, + resources: defaults.resources, + env: + if mysqlAllowEmptyPassword then [ + { + name: "MYSQL_ALLOW_EMPTY_PASSWORD", + value: "true", + }, + ] else [ + { + name: "MYSQL_ROOT_PASSWORD", + valueFrom: { + secretKeyRef: { + name: secretKeyName, + key: "mysql-root-password", + } + }, + }, + { + name: "MYSQL_PASSWORD", + valueFrom: { + secretKeyRef: { + name: secretKeyName, + key: "mysql-password", + }, + }, + }, + ] + [ + { + name: "MYSQL_USER", + value: if mysqlUser != null then mysqlUser else "", + }, + { + name: "MYSQL_DATABASE", + value: if mysqlDatabase != null then mysqlDatabase else "", + }, + ], + ports: [ + { + name: "mysql", + containerPort: 3306, + }, + ], + livenessProbe: { + exec: { + command: + if mysqlAllowEmptyPassword then [ + "mysqladmin", + "ping", + ] else [ + "sh", + "-c", + "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}", + ], + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + }, + readinessProbe: { + exec: { + command: + if mysqlAllowEmptyPassword then [ + "mysqladmin", + "ping", + ] else [ + "sh", + "-c", + "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}", + ], + }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + volumeMounts: [ + { + name: "data", + mountPath: "/var/lib/mysql", + [if subPath != null then "subPath"]: subPath, + }, + ] + if "configurationFiles" != null then [ + { + name: "configurations", + mountPath: "/etc/mysql/conf.d", + } + ] else [], + }, + ], + volumes: + if "configurationFiles" != null then [ + { + name: "configurations", + configMap: { + name: name, + // using the app name here though this could be expecting the configmap name + // my expectation would be that the config map has the same name + }, + }, + ] else [] + [ + if persistenceEnabled then { + name: "data", + persistentVolumeClaim: { + claimName: + if claimName != null + then claimName + else name, + }, + } else { + name: "data", + emptyDir: {}, + } + ], + }, + }, + }, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/mysql/parts.yaml b/pkg/registry/testdata/incubator/mysql/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..14f5ae29f99003a2748ce54f19e4a4571f918f86 --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/parts.yaml @@ -0,0 +1,40 @@ +{ + "name": "mysql", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "MySQL is one of the most popular database servers in the world. Notable users include Wikipedia, Facebook and Google.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "mysql", + "database", + "relational" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.simple-mysql", + "componentName": "mysql", + "flags": { + "name": "mysql", + "namespace": "default" + }, + "comment": "Run a simple MySQL server" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet b/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..d1fc1d1c41e153eb7bc0e34b7f013dc6a2727e2a --- /dev/null +++ b/pkg/registry/testdata/incubator/mysql/prototypes/simple-mysql.jsonnet @@ -0,0 +1,26 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.simple-mysql +// @description Deploys MySQL backed by a persistent volume. The MySQL container is deployed +// using a deployment and exposed to the network with a service. The +// passwords are stored in a secret. +// @shortDescription A simple MySQL deployment, backed by persistent storage. +// @param namespace string Namespace in which to put the application +// @param name string Name to give to each of the components +// @param mysqlRootPassword string Password for root user +// @param mysqlPassword string Password for new user + +local k = import 'k.libsonnet'; +local mysql = import '../mysql.libsonnet'; + +local namespace = import 'param://namespace'; +local name = import 'param://name'; +local mysqlRootPassword = import 'param://mysqlRootPassword'; +local mysqlPassword = import 'param://mysqlPassword'; + +k.core.v1.list.new([ + mysql.parts.configMap(namespace, name), + mysql.parts.deployment.persistent(namespace, name, name, name), + mysql.parts.pvc(namespace, name), + mysql.parts.secret(namespace, name, mysqlPassword, mysqlRootPassword), + mysql.parts.svc(namespace, name) +]) diff --git a/pkg/registry/testdata/incubator/nginx/README.md b/pkg/registry/testdata/incubator/nginx/README.md new file mode 100644 index 0000000000000000000000000000000000000000..443a766582924420445edf05e08a1e0f30b2f89e --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/README.md @@ -0,0 +1,82 @@ +# nginx + +> Nginx is an open source reverse proxy server that supports HTTP, HTTPS, SMTP, POP3, and IMAP protocols. It can be used as a load balancer, HTTP cache, and web server. It is designed for high concurrency, high performance, and low memory usage. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.nginx-simple](#io.ksonnet.pkg.nginx-simple) + * [io.ksonnet.pkg.nginx-server-block](#io.ksonnet.pkg.nginx-server-block) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.nginx-simple` prototype to generate Kubernetes YAML for nginx, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nginx-simple nginx \ + --name nginx \ + --namespace default + +# Apply to server. +$ ks apply -f nginx.jsonnet +``` + +## Using the library + +The library files for nginx define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure nginx for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of nginx, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.nginx-simple + +Deploys a simple, stateless nginx server. The nginx container is deployed using a Kubernetes deployment, and is exposed to a network with a +service. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nginx-simple nginx \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--name=<name>`: Name to give to each of the components [string] + +### io.ksonnet.pkg.nginx-server-block + +Deploys a simple, stateless nginx server with server blocks (roughly equivalent to nginx virtual hosts). The nginx container is deployed using a +Kubernetes deployment, and is exposed to a network with a service. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nginx-server-block nginx \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Name to give to all components. [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/nginx/examples/generated.yaml b/pkg/registry/testdata/incubator/nginx/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e08c62958f78d2719ae695a3c9e10eec0e6040ad --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/examples/generated.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: nginx-app + name: nginx-app +spec: + replicas: 1 + template: + metadata + labels: + app: nginx-app + spec: + containers: + - image: 'bitnami/nginx:1.10.2-r3' + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: / + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: nginx-app + ports: + - containerPort: 80 + name: http + - containerPort: 443 + name: https + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - mountPath: /bitnami/nginx + name: nginx-data + - mountPath: /bitnami/nginx/conf/vhosts + name: nginx-vhost + volumes: + - emptyDir: {} + name: nginx-data + - configMap: + name: nginx-app + name: nginx-vhost +--- +apiVersion: v1 +data: + vhost.conf: | + server { + listen 0.0.0.0:80; + root /app; + location / { + index index.html index.php; + } + location ~ \.php$ { + fastcgi_pass phpfpm-server:9000; + fastcgi_index index.php; + include fastcgi.conf; + } + } +kind: ConfigMap +metadata: + labels: + app: nginx-app + name: nginx-app diff --git a/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet b/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..ebd45c79a71c6753ce9fcc36ef63896ef02e2c6b --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/examples/nginx.jsonnet @@ -0,0 +1,11 @@ +local k = import 'k.libsonnet'; +local nginx = import '../nginx.libsonnet'; + +local namespace = "dev-alex"; +local appName = "nginx-app"; + +k.core.v1.list.new([ + nginx.parts.deployment.withServerBlock(namespace, appName), + nginx.parts.service(namespace, appName), + nginx.parts.serverBlockConfigMap(namespace, appName), +]) diff --git a/pkg/registry/testdata/incubator/nginx/nginx.libsonnet b/pkg/registry/testdata/incubator/nginx/nginx.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..a0123797a52fe22182952ad6c535001a88fb69c5 --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/nginx.libsonnet @@ -0,0 +1,149 @@ +local k = import 'k.libsonnet'; +local deployment = k.extensions.v1beta1.deployment; +local container = deployment.mixin.spec.template.spec.containersType; + +{ + parts:: { + deployment:: { + local defaults = { + imageTag: "1.10.2-r3", + imagePullPolicy: "IfNotPresent", + }, + + simple(namespace, name, labels={app: name}):: + base(namespace, name, labels), + + withServerBlock(namespace, name, configMapName="nginx-vhost", labels={app: name}):: + local volume = { + name:: configMapName, + configMap:: { name: name }, + }; + local dataMount = { + name:: configMapName, + mountPath:: "/bitnami/nginx/conf/vhosts", + }; + base(namespace, name, labels) + + deployment.mixin.spec.template.spec.withVolumes(volume) + + deployment.mapContainersWithName( + [name], + function(c) c + container.withVolumeMounts(dataMount) + ), + + local base(namespace, name, labels) = { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + spec: { + replicas: 1, + template: { + metadata: { labels: labels }, + spec: { + containers: [{ + name: name, + image: "bitnami/nginx:" + defaults.imageTag, + imagePullPolicy: defaults.imagePullPolicy, + ports: [ + { + name: "http", + containerPort: 80, + }, + { + name: "https", + containerPort: 443, + }, + ], + livenessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 5, + timeoutSeconds: 3, + periodSeconds: 5, + }, + volumeMounts: [{ + name: "nginx-data", + mountPath: "/bitnami/nginx", + }] + }], + volumes: [{ + name: "nginx-data", + emptyDir: {}, + }] + }, + }, + }, + }, + }, + + service(namespace, name, selector={app: name}):: { + apiVersion: "v1", + kind: "Service", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + spec: { + type: "LoadBalancer", + ports: [ + { + name: "http", + port: 80, + targetPort: "http", + }, + { + name: "https", + port: 443, + targetPort: "https", + }, + ], + selector: selector, + }, + }, + + serverBlockConfigMap(namespace, name):: { + local defaults = { + // example PHP-FPM vhost + vhost: + ||| + server { + listen 0.0.0.0:80; + root /app; + location / { + index index.html index.php; + } + location ~ \.php$ { + fastcgi_pass phpfpm-server:9000; + fastcgi_index index.php; + include fastcgi.conf; + } + } + ||| + }, + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + namespace: namespace, + name: name, + labels: { app: name }, + }, + data: { + "vhost.conf": defaults.vhost, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/nginx/parts.yaml b/pkg/registry/testdata/incubator/nginx/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..57b85949034869a76d427f27fa668a0832522179 --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/parts.yaml @@ -0,0 +1,41 @@ +{ + "name": "nginx", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Nginx is an open source reverse proxy server that supports HTTP, HTTPS, SMTP, POP3, and IMAP protocols. It can be used as a load balancer, HTTP cache, and web server. It is designed for high concurrency, high performance, and low memory usage.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "nginx", + "server", + "vhost", + "server block" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.nginx-simple", + "componentName": "nginx", + "flags": { + "name": "nginx", + "namespace": "default" + }, + "comment": "Run a simple NGINX server" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..a777ed3e66e223e97858b71d6be6be58b4019ede --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-server-block.jsonnet @@ -0,0 +1,22 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.nginx-server-block +// @description Deploys a simple, stateless nginx server with server blocks (roughly equivalent +// to nginx virtual hosts). The nginx container is deployed using a +// Kubernetes deployment, and is exposed to a network with a service. +// @shortDescription A simple, stateless nginx server with server blocks. +// @param namespace string Namespace in which to put the application +// @param name string Name to give to all components. + +// TODO: How should the ServerBlockConf be exposed to the user? Not quite sure what the default does except for setting web server to port 80. + +local k = import 'k.libsonnet'; +local nginx = import 'incubator/nginx/nginx.libsonnet'; + +local namespace = import 'param://namespace'; +local appName = import 'param://name'; + +k.core.v1.list.new([ + nginx.parts.deployment.withServerBlock(namespace, appName), + nginx.parts.service(namespace, appName), + nginx.parts.serverBlockConfigMap(namespace, appName), +]) diff --git a/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..5198d6118d07d4a2f2d116803cb6c394d1242413 --- /dev/null +++ b/pkg/registry/testdata/incubator/nginx/prototypes/nginx-simple.jsonnet @@ -0,0 +1,20 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.nginx-simple +// @description Deploys a simple, stateless nginx server. The nginx container is +// deployed using a Kubernetes deployment, and is exposed to a network with a +// service. +// @shortDescription A simple, stateless nginx server. +// @optionalParam namespace string default Namespace in which to put the application +// @param name string Name to give to each of the components + + +local k = import 'k.libsonnet'; +local nginx = import 'incubator/nginx/nginx.libsonnet'; + +local namespace = import 'param://namespace'; +local appName = import 'param://name'; + +k.core.v1.list.new([ + nginx.parts.deployment.simple(namespace, appName), + nginx.parts.service(namespace, appName), +]) diff --git a/pkg/registry/testdata/incubator/node/README.md b/pkg/registry/testdata/incubator/node/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3d3b06d9c4c208e19d939cb76c4ba6f778453760 --- /dev/null +++ b/pkg/registry/testdata/incubator/node/README.md @@ -0,0 +1,82 @@ +# nodejs + +> Node is an event-driven I/O server-side JavaScript environment based on V8 + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.nodejs-simple](#io.ksonnet.pkg.nodejs-simple) + * [io.ksonnet.pkg.nodejs-nonpersistent](#io.ksonnet.pkg.nodejs-nonpersistent) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.nodejs-simple` prototype to generate Kubernetes YAML for nodejs, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nodejs-simple nodejs \ + --name nodejs \ + --namespace default + +# Apply to server. +$ ks apply -f nodejs.jsonnet +``` + +## Using the library + +The library files for nodejs define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure nodejs for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of nodejs, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.nodejs-simple + +Deploy a node.js server with persistent volumes. The node container is deployed using a deployment, and exposed to the network using a service. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nodejs-simple nodejs \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace to specify location of app; default is 'default' [string] +* `--name=<name>`: Name of app to identify all K8s objects in this prototype [string] + +### io.ksonnet.pkg.nodejs-nonpersistent + +Deploy a node.js server with no persistent volumes. The node container is deployed using a deployment, and exposed to the network using a service. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.nodejs-nonpersistent nodejs \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace to specify location of app; default is 'default' [string] +* `--name=<name>`: Name of app to identify all K8s objects in this prototype [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/node/examples/generated.yaml b/pkg/registry/testdata/incubator/node/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b24f3f59e52bc74c37872321869d7cfb8d4d7196 --- /dev/null +++ b/pkg/registry/testdata/incubator/node/examples/generated.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: node-app + name: node-app + namespace: default +spec: + replicas: 1 + template: + metadata: + annotations: + pod.beta.kubernetes.io/init-containers: '[{"command": "[ /bin/sh, -c , git + clone https://github.com/jbianquetti-nami/simple-node-app.git /app && git + checkout 26679f6]", "image": "bitnami/node:8.4.0-r1", "imagePullPolicy": + "IfNotPresent", "name": "git-clone-app", "volumeMounts": [{"mountPath": + "/app", "name": "app"}]}, {"command": "[ \"npm\", \"install\" ]", "image": + "bitnami/node:8.4.0-r1", "imagePullPolicy": "IfNotPresent", "name": "npm-install", + "volumeMounts": [{"mountPath": "/app", "name": "app"}]}]' + labels: + app: node-app + test: it worked + spec: + containers: + - command: + - npm + - start + env: + - name: GIT_REPO + value: https://github.com/jbianquetti-nami/simple-node-app.git + image: bitnami/node:8.4.0-r1 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: / + port: http + initialDelaySeconds: 180 + timeoutSeconds: 5 + name: node-app + ports: + - containerPort: 3000 + name: http + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 3 + resources: + requests: + cpu: 300m + memory: 512Mi + securityContext: + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /app + name: app + - mountPath: /app/data + name: data + volumes: + - emptyDir: {} + name: app + - name: data + persistentVolumeClaim: + claimName: node-app +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + volume.alpha.kubernetes.io/storage-class: default + labels: + app: node-app + name: node-app + namespace: default +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: node-app + name: node-app + namespace: default +spec: + ports: + - name: http + port: 80 + targetPort: http + selector: + app: node-app + type: LoadBalancer diff --git a/pkg/registry/testdata/incubator/node/nodejs.libsonnet b/pkg/registry/testdata/incubator/node/nodejs.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..ba6c039056dc53d66e68e890048c7b7baf27ce9f --- /dev/null +++ b/pkg/registry/testdata/incubator/node/nodejs.libsonnet @@ -0,0 +1,210 @@ +local k = import "k.libsonnet"; +local deployment = k.extensions.v1beta1.deployment; + +{ + parts:: { + pvc(namespace, name, storageClassName=null):: { + local defaults = { + size:: "1Gi", + accessMode:: "ReadWriteOnce", + }, + + apiVersion: "v1", + kind: "PersistentVolumeClaim", + metadata: { + namespace: namespace, + name: name, + labels: { + app: name, + }, + annotations: + if storageClassName == null + then { + "volume.alpha.kubernetes.io/storage-class": "default", + } else { + "volume.beta.kubernetes.io/storage-class": storageClassName, + }, + }, + spec: { + accessModes: [ + defaults.accessMode, + ], + resources: { + requests: { + storage: defaults.size, + }, + }, + }, + }, + + svc(namespace, name, selector={app: name}):: { + local defaults = { + serviceType:: "LoadBalancer", + }, + + apiVersion: "v1", + kind: "Service", + metadata: { + namespace: namespace, + name: name, + labels: { + app: name, + }, + }, + spec: { + type: defaults.serviceType, + ports: [ + { + name: "http", + port: 80, + targetPort: "http", + }, + ], + selector: selector, + }, + }, + + deployment:: { + local defaults = { + image:: "bitnami/node:8.4.0-r1", + repository:: "https://github.com/jbianquetti-nami/simple-node-app.git", + revision:: "26679f6", + imagePullPolicy:: "IfNotPresent", + resources:: { + "requests": { + "memory": "512Mi", + "cpu": "300m", + }, + }, + mountPath:: "/app/data", + labels(name):: {"app": name}, + }, + + // Note: volumes are added to deployment as overlays based on persistence + persistent(namespace, name, labels=defaults.labels(name), claimName=name):: + local volume = { + name: "data", + persistentVolumeClaim: { + claimName: claimName + }, + }; + base(namespace, name, labels) + + deployment.mixin.spec.template.spec.withVolumes(volume), + + nonPersistent(namespace, name, labels=defaults.labels(name)):: + local volume = { + name: "data", + emptyDir: {}, + }; + base(namespace, name, labels) + + deployment.mixin.spec.template.spec.withVolumes(volume), + + local base(namespace, name, labels) = { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + namespace: namespace, + name: name, + labels: labels + }, + spec: { + replicas: 1, + template: { + metadata: { + labels: labels, + annotations: { + "pod.beta.kubernetes.io/init-containers": std.toString([ + { + name: "git-clone-app", + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + command: "[ /bin/sh, -c , git clone " + defaults.repository+ " /app && git checkout " + defaults.revision + "]", + volumeMounts: [ + { + name: "app", + mountPath: "/app" + } + ], + }, + { + name: "npm-install", + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + command: '[ "npm", "install" ]', + volumeMounts: [ + { + name: "app", + mountPath: "/app" + } + ], + }, + ]), + }, + test: "it worked", + }, + spec: { + containers: [ + { + name: name, + securityContext: { + readOnlyRootFilesystem: true, + }, + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + env: [ + { + name: "GIT_REPO", + value: defaults.repository, + }, + ], + command: [ "npm", "start" ], + ports: [ + { + name: "http", + containerPort: 3000, + } + ], + livenessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 180, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 30, + timeoutSeconds: 3, + periodSeconds: 5, + }, + resources: defaults.resources, + volumeMounts: [ + { + name: "app", + mountPath: "/app", + }, + { + name: "data", + mountPath: defaults.mountPath, + } + ], + } + ], + volumes: [ + { + name: "app", + emptyDir: {}, + }, + ], + } + }, + }, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/node/parts.yaml b/pkg/registry/testdata/incubator/node/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6622a3ca94f6739d584c03cd73fe77670b25841d --- /dev/null +++ b/pkg/registry/testdata/incubator/node/parts.yaml @@ -0,0 +1,41 @@ +{ + "name": "nodejs", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Node is an event-driven I/O server-side JavaScript environment based on V8", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "nodejs", + "node", + "server", + "javascript" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.nodejs-simple", + "componentName": "nodejs", + "flags": { + "name": "nodejs", + "namespace": "default" + }, + "comment": "Run a simple NodeJS server" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet b/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..5303a2e5859edef298c1f85ef5465b49fc26f8de --- /dev/null +++ b/pkg/registry/testdata/incubator/node/prototypes/nodejs-nonpersistent.jsonnet @@ -0,0 +1,18 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.nodejs-nonpersistent +// @description Deploy a node.js server with no persistent volumes. The node container is +// deployed using a deployment, and exposed to the network using a service. +// @shortDescription A simple, stateless NodeJS app server. +// @param namespace string Namespace to specify location of app; default is 'default' +// @param name string Name of app to identify all K8s objects in this prototype + +local k = import 'k.libsonnet'; +local nodeJS = import 'incubator/node/nodejs.libsonnet'; + +local namespace = "import 'param://namespace'"; +local appName = "import 'param://name'"; + +k.core.v1.list.new([ + nodeJS.parts.deployment.nonPersistent(namespace, appName), + nodeJS.parts.svc(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet b/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..5c3f064745e83b5f697c8354df8424aa8f18091c --- /dev/null +++ b/pkg/registry/testdata/incubator/node/prototypes/nodejs-simple.jsonnet @@ -0,0 +1,20 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.nodejs-simple +// @description Deploy a node.js server with persistent volumes. The node container is +// deployed using a deployment, and exposed to the network using a service. +// @shortDescription A simple NodeJS app server with persistent storage. +// @param namespace string Namespace to specify location of app; default is 'default' +// @param name string Name of app to identify all K8s objects in this prototype + + +local k = import 'k.libsonnet'; +local nodeJS = import 'incubator/node/nodejs.libsonnet'; + +local appName = import 'param://name'; +local namespace = import 'param://namespace'; + +k.core.v1.list.new([ + nodeJS.parts.deployment.persistent(namespace, appName), + nodeJS.parts.pvc(namespace, appName), + nodeJS.parts.svc(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/postgres/README.md b/pkg/registry/testdata/incubator/postgres/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f94bc77e999afc75b125c29bcdf828d7d8085ca7 --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/README.md @@ -0,0 +1,71 @@ +# postgres + +> PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness. + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.postgres-simple](#io.ksonnet.pkg.postgres-simple) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.postgres-simple` prototype to generate Kubernetes YAML for postgres, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.postgres-simple postgres \ + --name postgres \ + --namespace default \ + --password boots + +# Apply to server. +$ ks apply -f postgres.jsonnet +``` + +## Using the library + +The library files for postgres define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure postgres for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of postgres, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.postgres-simple + +Deploy postgres backed by a persistent volume. Postgres container is managed by a deployment object and exposed to the network with a service. The +passwords are stored in a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.postgres-simple postgres \ + --name YOUR_NAME_HERE \ + --namespace YOUR_NAMESPACE_HERE \ + --password YOUR_PASSWORD_HERE +``` + +Below is the Jsonnet file generated by this command. + +``` +// postgres.jsonnet +<JSONNET HERE> +``` + +#### Parameters + +The available options to pass prototype are: + +* `--name=<name>`: Name of app to attach as identifier to all components [string] +* `--namespace=<namespace>`: Namespace to specify destination in cluster; default is 'default' [string] +* `--password=<password>`: Password for the root/admin user. [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/postgres/examples/generated.yaml b/pkg/registry/testdata/incubator/postgres/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6c29c2388aaf0ecc7a3504db02705c4075ea4bbb --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/examples/generated.yaml @@ -0,0 +1,136 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: postgres-app + name: postgres-app + namespace: dev-alex +spec: + template: + metadata: + labels: + app: postgres-app + spec: + containers: + - env: + - name: POSTGRES_USER + value: postgres + - name: PGUSER + value: postgres + - name: POSTGRES_DB + value: "" + - name: POSTGRES_INITDB_ARGS + value: "" + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + key: postgres-password + name: postgres-app + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + image: postgres:9.6.2 + imagePullPolicy: Always + livenessProbe: + exec: + command: + - sh + - -c + - exec pg_isready --host $POD_IP + failureThreshold: 6 + initialDelaySeconds: 60 + timeoutSeconds: 5 + name: postgres-app + ports: + - containerPort: 5432 + name: postgresql + readinessProbe: + exec: + command: + - sh + - -c + - exec pg_isready --host $POD_IP + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /var/lib/postgresql/data/pgdata + name: data + subPath: postgresql-db + nodeSelector: {} + tolerations: [] + volumes: + - name: data + persistentVolumeClaim: + claimName: postgres-app +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app: postgres-app + name: postgres-app + namespace: dev-alex +spec: + ingress: + - from: + - podSelector: + matchLabels: + postgres-app-client: "true" + ports: + - port: 5432 + - ports: + - port: 9187 + podSelector: + matchLabels: + app: postgres-app +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app: postgres-app + name: postgres-app + namespace: dev-alex +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: v1 +data: + postgres-password: c2ltcGxlUGFzc3dvcmQ= +kind: Secret +metadata: + labels: + app: postgres-app + name: postgres-app + namespace: dev-alex +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: postgres-app + name: postgres-app + namespace: dev-alex +spec: + externalIPs: [] + ports: + - name: postgresql + port: 5432 + targetPort: postgresql + selector: + app: postgres-app + type: ClusterIP diff --git a/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet b/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..41e97e43649e2f19c142b05ce4bb15298cca5e26 --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/examples/postgres.jsonnet @@ -0,0 +1,11 @@ +local k = import 'k.libsonnet'; +local service = k.core.v1.service.mixin; +local psg = import '../postgres.libsonnet'; + +k.core.v1.list.new([ + psg.parts.deployment.persistent('dev-alex', "postgress-app"), + psg.parts.networkPolicy.allowExternal('dev-alex', true), + psg.parts.pvc('dev-alex', "postgress-app"), + psg.parts.secrets('dev-alex', "postgress-app", "GOODPASSWORD"), + psg.parts.service('dev-alex', "postgress-app", false), +]) diff --git a/pkg/registry/testdata/incubator/postgres/parts.yaml b/pkg/registry/testdata/incubator/postgres/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ca71e49ad27e9cf058e09efc48c9b640f10712a1 --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/parts.yaml @@ -0,0 +1,40 @@ +{ + "name": "postgres", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness.", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "postgres", + "postgresql", + "database" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.postgres-simple", + "componentName": "postgres", + "flags": { + "name": "posgres", + "namespace": "default", + "password": "boots" + } + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/postgres/postgres.libsonnet b/pkg/registry/testdata/incubator/postgres/postgres.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..4f6fdddfca24acf49b0ab6a028432dc9b81aa408 --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/postgres.libsonnet @@ -0,0 +1,313 @@ +local k = import 'k.libsonnet'; +local service = k.core.v1.service.mixin; + +{ + parts:: { + service(namespace, name, metricsEnabled=false, externalIpArray=null, selector={app:name}):: + local defaults= { + type: 'ClusterIP', + port: 5432 + }; + + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name, + }, + [if metricsEnabled then "annotations"]: { + "prometheus.io/scrape": "true", + "prometheus.io/port": "9187" + }, + }, + spec: { + type: defaults.type, + ports: [ + { + name: "postgresql", + port: defaults.port, + targetPort: "postgresql", + } + ], + [if externalIpArray != null then "externalIPs"]: + externalIpArray, + selector: selector + }, + }, + + pvc(namespace, name, storageClassName="-", labels={app:name}):: + local defaults = { + accessMode:"ReadWriteOnce", + size: "8Gi" + }; + + { + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + spec: { + accessModes: [ + defaults.accessMode + ], + resources: { + requests: { + storage: defaults.size, + }, + }, + storageClassName: storageClassName + }, + }, + + secrets(namespace, name, postgresPassword, labels={app:name}):: + { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + namespace: namespace, + labels: labels, + }, + type: "Opaque", + data: { + "postgres-password": std.base64(postgresPassword), + }, + }, + + networkPolicy:: { + allowExternal(namespace, name, allowInbound=false, labels={app:name}, podSelector={matchLabels: {app: name}}):: + base(namespace, name, allowInbound, labels, podSelector), + + local base(namespace, name, allowInbound, labels, podSelector) = { + kind: "NetworkPolicy", + apiVersion: "networking.k8s.io/v1", + metadata: { + name: name, + namespace: namespace, + labels: labels + }, + spec: { + podSelector: podSelector, + ingress: [ + { + # Allow inbound connections + ports: [ + {port: 5432}, + ], + [if allowInbound then "from"]:[ + { + podSelector: { + matchLabels: { + [name + "-client"]: "true", + }, + }, + }, + ], + }, + { + # Allow prometheus scrapes + ports: [ + {port: 9187}, + ] + } + ], + } + }, + }, + deployment::{ + local defaults ={ + image: "postgres", + imageTag: "9.6.2", + imagePullPolicy: "Always", + persistence::{ + enabled: true, + accessMode:"ReadWriteOnce", + size:"8Gi", + subPath:"postgresql-db" + }, + resources:: { + requests: { + memory: "256Mi", + cpu: "100m" + } + }, + metrics::{ + enabled: false, + image: "wrouesnel/postgres_exporter", + imageTag: "v0.1.1", + imagePullPolicy: "IfNotPresent", + resources: { + requests: { + memory: "256Mi", + cpu: "100m" + } + } + }, + + postgresConfig:: { + user:: "postgres", + db:: "", + initDbArgs:: "", + }, + }, + + persistent(namespace, name, pgConfig=defaults.postgresConfig, metricsEnabled=false, existingClaim=name, labels={app:name}): + local volume = { + name: "data", + persistentVolumeClaim: { + claimName: existingClaim + } + }; + base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) + + k.extensions.v1beta1.deployment.mixin.spec.template.spec.withVolumes(volume), + + nonPersistent(namespace, name, pgConfig=defaults.postgresConfig, metricsEnabled=false, existingClaim=name, labels={app:name}):: + local volume = { + name: "data", + emptyDir: {} + }; + base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) + + k.extensions.v1beta1.deployment.mixin.spec.template.spec.withVolumes(volume), + + local base(namespace, name, pgConfig, metricsEnabled, existingClaim, labels) = + local metricsContainer = [ + { + name: "metrics", + image: defaults.metrics.image + ":" + defaults.metrics.imageTag, + imagePullPolicy: defaults.metrics.imagePullPolicy, + env: [ + { + name: "DATA_SOURCE_NAME", + value: "postgresql://postgres@127.0.0.1:5432?sslmode=disable", + }, + ], + ports: [ + { + name: "metrics", + containerPort: 9187, + }, + ], + resources: defaults.metrics.resources + } + ]; + + { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + spec: { + template: { + metadata: { + labels: { + app: name, + } + }, + spec: { + containers: [ + { + name: name, + image: defaults.image + ":" + defaults.imageTag, + imagePullPolicy: defaults.imagePullPolicy, + env: [ + { + name: "POSTGRES_USER", + value: pgConfig.user + }, + { + # Required for pg_isready in the health probes. + name: "PGUSER", + value: + pgConfig.user + }, + { + name: "POSTGRES_DB", + value: pgConfig.db + }, + { + name: "POSTGRES_INITDB_ARGS", + value: pgConfig.initDbArgs + }, + { + name: "PGDATA", + value: "/var/lib/postgresql/data/pgdata", + }, + { + name: "POSTGRES_PASSWORD", + valueFrom: { + secretKeyRef: { + name: name, + key: "postgres-password" + }, + }, + }, + { + name: "POD_IP", + valueFrom: { + fieldRef: { + fieldPath: "status.podIP", + } + } + } + ], + ports: [ + { + name: "postgresql", + containerPort: 5432, + }, + ], + livenessProbe: { + exec: { + command: [ + "sh", + "-c", + "exec pg_isready --host $POD_IP", + ], + }, + initialDelaySeconds: 60, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + exec: { + command: [ + "sh", + "-c", + "exec pg_isready --host $POD_IP", + ], + }, + initialDelaySeconds: 5, + timeoutSeconds: 3, + periodSeconds: 5, + }, + resources: defaults.metrics.resources, + volumeMounts: [ + { + name: "data", + mountPath: "/var/lib/postgresql/data/pgdata", + subPath: defaults.persistence.subPath + }, + ], + }, + ] + + if metricsEnabled then metricsContainer + else [], + volumes: [], + }, + }, + }, + }, + } + } +} diff --git a/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet b/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..1e223c89f8bd805747caf47b4535327b4fdc3d95 --- /dev/null +++ b/pkg/registry/testdata/incubator/postgres/prototypes/postgres-simple.jsonnet @@ -0,0 +1,23 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.postgres-simple +// @description Deploy postgres backed by a persistent volume. Postgres container is managed by +// a deployment object and exposed to the network with a service. The +// passwords are stored in a secret. +// @shortDescription A simple Postgres deployment, backed by persistent storage. +// @param name string Name of app to attach as identifier to all components +// @param namespace string Namespace to specify destination in cluster; default is 'default' +// @param password string Password for the root/admin user. + +local k = import 'k.libsonnet'; +local psg = import 'incubator/postgres/postgres.libsonnet'; + +local appName = import 'param://name'; +local namespace = import 'param://namespace'; +local password = import 'param://password'; + +k.core.v1.list.new([ + psg.parts.deployment.persistent(namespace, appName), + psg.parts.pvc(namespace, appName), + psg.parts.secrets(namespace, appName, password), + psg.parts.service(namespace, appName) +]) diff --git a/pkg/registry/testdata/incubator/redis/README.md b/pkg/registry/testdata/incubator/redis/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cafb05a9b2baf81e3fbfc596ea632a626850b281 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/README.md @@ -0,0 +1,101 @@ +# redis + +> Redis is an advanced key-value cache and store. Often referred to as a data structure server since keys can contain structures as simple as strings, hashes and as complex as bitmaps and hyperloglogs. This package will deploy redis backed by a mounted persistent volume, a secret to hold your database password and a service to expose your deployment. + + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.redis-stateless](#io.ksonnet.pkg.redis-stateless) + * [io.ksonnet.pkg.redis-persistent](#io.ksonnet.pkg.redis-persistent) + * [io.ksonnet.pkg.redis-all-features](#io.ksonnet.pkg.redis-all-features) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.redis-persistent` prototype to generate Kubernetes YAML for redis, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.redis-persistent redis \ + --name redis \ + --namespace default \ + --password boots + +# Apply to server. +$ ks apply -f redis.jsonnet +``` + +## Using the library + +The library files for redis define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure redis for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of redis, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.redis-stateless + +Stateless redis, backed with NO persistent volume claim. Redis is deployed using a Kubernetes deployment, exposed to the network with a service, with +a password stored in a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.redis-stateless redis \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--name=<name>`: Name to give to each of the components. [string] + +### io.ksonnet.pkg.redis-persistent + +Redis backed by a persistent volume claim. Redis is deployed using a Kubernetes deployment, exposed to the network with a service, with a password stored in a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.redis-persistent redis \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--name=<name>`: Name to give to each of the components [string] + +### io.ksonnet.pkg.redis-all-features + +Redis with all the features supported by redis.libsonnet (e.g. secret, metrics, ingress, PVC) + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.redis-all-features redis \ + --name YOUR_NAME_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--name=<name>`: Name to give to each of the components [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/redis/examples/generated.yaml b/pkg/registry/testdata/incubator/redis/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c0023986a687c8fbdc630710ab03e003310b69bb --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/examples/generated.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: redis-app + name: redis-app + namespace: dev-alex +spec: + template: + metadata: + labels: + app: redis-app + spec: + containers: + - env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + key: redis-password + name: redis-app + image: bitnami/redis:3.2.9-r2 + imagePullPolicy: IfNotPresent + livenessProbe: + exec: + command: + - redis-cli + - ping + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis-app + ports: + - containerPort: 6379 + name: redis + readinessProbe: + exec: + command: + - redis-cli + - ping + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /bitnami/redis + name: redis-data + - env: + - name: REDIS_ALIAS + value: redis-app + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + key: redis-password + name: redis-app + image: oliver006/redis_exporter:v0.11 + imagePullPolicy: IfNotPresent + name: metrics + ports: + - containerPort: 9121 + name: metrics + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-app +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app: redis-app + name: redis-app + namespace: dev-alex +spec: + ingress: + - ports: + - port: 9121 + - from: + - podSelector: null + ports: + - port: 6379 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app: redis-app + name: redis-app + namespace: dev-alex +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi + storageClassName: '-' +--- +apiVersion: v1 +data: + redis-password: Wm05dlltRnk= +kind: Secret +metadata: + labels: + app: redis-app + name: redis-app + namespace: dev-alex +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + prometheus.io/port: "9121" + prometheus.io/scrape: "true" + labels: + app: redis-app + name: redis-app + namespace: dev-alex +spec: + ports: + - name: redis + port: 6379 + targetPort: redis + selector: + app: redis-app diff --git a/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet b/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..b06295ac5721e34e72e3208b474b76ef46dc0970 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/examples/redis.jsonnet @@ -0,0 +1,10 @@ +local k = import 'k.libsonnet'; +local redis = import '../redis.libsonnet'; + +k.core.v1.list.new([ +redis.parts.deployment.persistent("dev-alex", "redis-app","redis-app", true), + redis.parts.networkPolicy.allowExternal('dev-alex', "redis-app", true, true), + redis.parts.pvc('dev-alex', "redis-app", "-"), + redis.parts.secret('dev-alex', "redis-app", 'Zm9vYmFy'), + redis.parts.svc.metricEnabled("dev-alex", "redis-app"), +]) diff --git a/pkg/registry/testdata/incubator/redis/parts.yaml b/pkg/registry/testdata/incubator/redis/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f59cadc17b57043bb12cdc07e763adf01f1edfe6 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/parts.yaml @@ -0,0 +1,40 @@ +{ + "name": "redis", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Redis is an advanced key-value cache and store. Often referred to as a data structure server since keys can contain structures as simple as strings, hashes and as complex as bitmaps and hyperloglogs. This package will deploy redis backed by a mounted persistent volume, a secret to hold your database password and a service to expose your deployment.\n", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "redis", + "persistent", + "database" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.redis-persistent", + "componentName": "redis", + "flags": { + "name": "redis", + "namespace": "default", + "password": "boots" + }, + "comment": "Run simple Redis, backed by a PersistentVolume" + }, + "license": "Apache 2.0" +} diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..8eae75c2689a4dc46fd17bbf2ec6397122e73c94 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-all-features.jsonnet @@ -0,0 +1,29 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.redis-all-features +// @description Redis with all the features supported by redis.libsonnet +// (e.g. secret, metrics, ingress, PVC) +// @shortDescription A Redis deployment with metrics, ingress, and persistent storage. +// @param name string Name to give to each of the components +// @optionalParam redisPassword string null User password to supply to redis + +local k = import 'k.libsonnet'; +local redis = import 'incubator/redis/redis.libsonnet'; + +local name = import 'param://name'; +local redisPassword = import 'param://redisPassword'; + +local secretName = + if redisPassword != "null" then name else null; + +local optionalSecret = + if redisPassword != "null" + then redis.parts.secret(name, redisPassword) + else null; + +std.prune(k.core.v1.list.new([ + redis.parts.deployment.persistent(name, secretName), + redis.parts.networkPolicy.allowExternal(name, true, true), + redis.parts.pvc(name), + redis.parts.svc.metricEnabled(name), + optionalSecret, +])) diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..6628215d45cc2a8fc65dc8568687f3e315707887 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-persistent.jsonnet @@ -0,0 +1,29 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.redis-persistent +// @description Redis backed by a persistent volume claim. Redis is deployed using a Kubernetes +// deployment, exposed to the network with a service, with a password stored +// in a secret. +// @param name string Name to give to each of the components +// @shortDescription A simple Redis deployment, backed by persistent storage. +// @optionalParam redisPassword string null User password to supply to redis + +local k = import 'k.libsonnet'; +local redis = import 'incubator/redis/redis.libsonnet'; + +local name = import 'param://name'; +local redisPassword = import 'param://redisPassword'; + +local secretName = + if redisPassword != "null" then name else null; + +local optionalSecret = + if redisPassword != "null" + then redis.parts.secret(name, redisPassword) + else null; + +std.prune(k.core.v1.list.new([ + redis.parts.deployment.persistent(name, secretName), + redis.parts.pvc(name), + redis.parts.svc.metricDisabled(name), + optionalSecret, +])) diff --git a/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet b/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..163d1a2743328ff6acd14fe0fdc459ae79ac30fc --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/prototypes/redis-stateless.jsonnet @@ -0,0 +1,28 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.redis-stateless +// @description Stateless redis, backed with NO persistent volume claim. Redis is deployed +// using a Kubernetes deployment, exposed to the network with a service, with +// a password stored in a secret. +// @shortDescription A simple, stateless Redis deployment, +// @param name string Name to give to each of the components. +// @optionalParam redisPassword string null User password to supply to redis + +local k = import 'k.libsonnet'; +local redis = import 'incubator/redis/redis.libsonnet'; + +local name = import 'param://name'; +local redisPassword = import 'param://redisPassword'; + +local secretName = + if redisPassword != "null" then name else null; + +local optionalSecret = + if redisPassword != "null" + then redis.parts.secret(name, redisPassword) + else null; + +std.prune(k.core.v1.list.new([ + redis.parts.deployment.nonPersistent(name, secretName), + redis.parts.svc.metricDisabled(name), + optionalSecret, +])) diff --git a/pkg/registry/testdata/incubator/redis/redis.libsonnet b/pkg/registry/testdata/incubator/redis/redis.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..0ffb84230499c9705805e44965b844e0d8599332 --- /dev/null +++ b/pkg/registry/testdata/incubator/redis/redis.libsonnet @@ -0,0 +1,277 @@ +local k = import 'k.libsonnet'; +local deployment = k.extensions.v1beta1.deployment; +local container = deployment.mixin.spec.template.spec.containersType; +local storageClass = k.storage.v1beta1.storageClass; +local service = k.core.v1.service; +local networkPolicy = k.extensions.v1beta1.networkPolicy; +local networkSpec = networkPolicy.mixin.spec; + +{ + parts:: { + networkPolicy:: { + local defaults = { + inboundPort: { + ports: [{port:6379}] + }, + }, + + allowExternal(name, allowInbound, metricEnabled, podSelector=null, labels={app:name},):: + base(name, metricEnabled, podSelector, labels) + + networkSpec.withIngress(defaults.inboundPort), + + denyExternal(name, allowInbound, metricEnabled, podSelector={matchLabels:{[name + "-client"]: "true"}}, labels={app:name}, ):: + local ingressRule = defaults.inboundPort + { + from: [ + { + podSelector: podSelector + }, + ], + }; + base(name, metricEnabled, podSelector, labels)+ + networkSpec.withIngress(ingressRule), + + local base(name, metricEnabled, podSelector, labels) = { + kind: "NetworkPolicy", + apiVersion: "networking.k8s.io/v1", + metadata: { + name: name, + labels: labels, + }, + spec: { + [if podSelector != null then "podSelector"]: podSelector, + [if metricEnabled then "ingress"]: [ + { + # Allow prometheus scrapes for metrics + ports: [ + {port: 9121}, + ] + } + ], + }, + }, + }, + + secret(name, redisPassword, labels={app:name}):: + local defaults = { + usePassword: true, + }; + + { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + labels: labels, + }, + type: "Opaque", + data: { + "redis-password": std.base64(redisPassword), + }, + }, + + svc::{ + metricDisabled(name, labels={app:name}, selector={app:name}):: + svcBase(name, labels, selector), + + metricEnabled(name, labels={app:name}, selector={app:name}):: + local annotations = { + "prometheus.io/scrape": "true", + "prometheus.io/port": "9121" + }; + svcBase(name, labels, selector) + + service.mixin.metadata.withAnnotations(annotations), + + local svcBase(name, labels, selector)= { + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + labels: labels, + }, + spec: { + ports: [ + { + name: "redis", + port: 6379, + targetPort: "redis", + } + ], + selector: selector, + } + }, + }, + + pvc(name, storageClass="-", labels={app:name}):: + local defaults = { + accessMode: "ReadWriteOnce", + size: '8Gi' + }; + + { + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + name: name, + labels: labels + }, + spec: { + accessModes: [ + defaults.accessMode, + ], + storageClassName: storageClass, + resources: { + requests: { + storage: defaults.size, + }, + }, + }, + }, + + deployment:: { + local defaults = { + image:: "bitnami/redis:3.2.9-r2", + imagePullPolicy:: "IfNotPresent", + resources:: { + "requests": { + "memory": "256Mi", + "cpu": "100m" + }, + }, + dataMount:: { + name: "redis-data", + mountPath: "/bitnami/redis", + }, + metrics:: { + image: "oliver006/redis_exporter", + imageTag: "v0.11", + imagePullPolicy: "IfNotPresent", + }, + }, + + nonPersistent(name, secretName, metricEnabled=false, labels={app:name},): + local volume = { + name: "redis-data", + emptyDir: {} + }; + base(name, secretName, metricEnabled, labels) + + deployment.mixin.spec.template.spec.withVolumes(volume) + + deployment.mapContainersWithName( + [name], + function(c) c + container.withVolumeMounts(defaults.dataMount) + ), + + persistent(name, secretName, metricEnabled=false, claimName=name, labels={app:name}):: + local volume = { + name: "redis-data", + persistentVolumeClaim: { + claimName: claimName + } + }; + base(name, secretName, metricEnabled, labels) + + deployment.mixin.spec.template.spec.withVolumes(volume) + + deployment.mapContainersWithName( + [name], + function(c) c + container.withVolumeMounts(defaults.dataMount) + ), + + local base(name, secretName, metricsEnabled, labels) = + local metricsContainer = + if !metricsEnabled then [] + else [{ + name: "metrics", + image: defaults.metrics.image + ':' + defaults.metrics.imageTag, + imagePullPolicy: defaults.metrics.imagePullPolicy, + env: [ + { + name: "REDIS_ALIAS", + value: name, + } + ] + if secretName == null then [] + else [ + { + name: "REDIS_PASSWORD", + valueFrom: { + secretKeyRef: { + name: name, + key: "redis-password", + } + }, + }, + ], + ports: [ + { + name: "metrics", + containerPort: 9121, + } + ], + }]; + { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + name: name, + labels: labels, + }, + spec: { + template: { + metadata: { + labels: labels + }, + spec: { + containers: [ + { + name: name, + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + env: [ + if secretName != null then + { + name: "REDIS_PASSWORD", + valueFrom: { + secretKeyRef: { + name: secretName, + key: "redis-password", + }, + } + } + else{ + name: "ALLOW_EMPTY_PASSWORD", + value: "yes", + }, + ], + ports: [ + { + name: "redis", + containerPort: 6379, + }, + ], + livenessProbe: { + exec: { + command: [ + "redis-cli", + "ping", + ], + }, + initialDelaySeconds: 30, + timeoutSeconds: 5, + }, + readinessProbe: { + exec: { + command: [ + "redis-cli", + "ping", + ], + }, + initialDelaySeconds: 5, + timeoutSeconds: 1, + }, + resources: defaults.resources, + }, + ] + metricsContainer, + }, + }, + }, + }, + }, + }, +} diff --git a/pkg/registry/testdata/incubator/registry.yaml b/pkg/registry/testdata/incubator/registry.yaml new file mode 100644 index 0000000000000000000000000000000000000000..57e71c2d9cacce48baffb309c2fd718db4cf9ad0 --- /dev/null +++ b/pkg/registry/testdata/incubator/registry.yaml @@ -0,0 +1,36 @@ +apiVersion: '0.1' +kind: ksonnet.io/registry +libraries: + apache: + version: master + path: apache + efk: + version: master + path: efk + mariadb: + version: master + path: mariadb + memcached: + version: master + path: memcached + mongodb: + version: master + path: mongodb + mysql: + version: master + path: mysql + node: + version: master + path: node + nginx: + version: master + path: nginx + postgres: + version: master + path: postgres + redis: + version: master + path: redis + tomcat: + version: master + path: tomcat diff --git a/pkg/registry/testdata/incubator/tomcat/README.md b/pkg/registry/testdata/incubator/tomcat/README.md new file mode 100644 index 0000000000000000000000000000000000000000..39a111ee706ecf9fcfa0fc89a847cf1dfec1fc76 --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/README.md @@ -0,0 +1,93 @@ +# tomcat + +> Apache Tomcat, or Tomcat, is an open-source web server and servlet container. This package deploys a stateless tomcat container, service, and secret to your cluster + +* [Quickstart](#quickstart) +* [Using Prototypes](#using-prototypes) + * [io.ksonnet.pkg.non-persistent-tomcat](#io.ksonnet.pkg.non-persistent-tomcat) + * [io.ksonnet.pkg.persistent-tomcat](#io.ksonnet.pkg.persistent-tomcat) + +## Quickstart + +*The following commands use the `io.ksonnet.pkg.persistent-tomcat` prototype to generate Kubernetes YAML for tomcat, and then deploys it to your Kubernetes cluster.* + +First, create a cluster and install the ksonnet CLI (see root-level [README.md](rootReadme)). + +If you haven't yet created a [ksonnet application](linkToSomewhere), do so using `ks init <app-name>`. + +Finally, in the ksonnet application directory, run the following: + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.persistent-tomcat tomcat \ + --name tomcat \ + --namespace default \ + --tomcatUser frank \ + --tomcatPassword boots + +# Apply to server. +$ ks apply -f tomcat.jsonnet +``` + +## Using the library + +The library files for tomcat define a set of relevant *parts* (_e.g._, deployments, services, secrets, and so on) that can be combined to configure tomcat for a wide variety of scenarios. For example, a database like Redis may need a secret to hold the user password, or it may have no password if it's acting as a cache. + +This library provides a set of pre-fabricated "flavors" (or "distributions") of tomcat, each of which is configured for a different use case. These are captured as ksonnet *prototypes*, which allow users to interactively customize these distributions for their specific needs. + +These prototypes, as well as how to use them, are enumerated below. + +### io.ksonnet.pkg.non-persistent-tomcat + +Deploys a stateless Tomcat server. Server is deployed using a Kubernetes deployment, and exposed to the network using a service. The password is stored as a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.non-persistent-tomcat tomcat \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE \ + --tomcatUser YOUR_TOMCATUSER_HERE \ + --tomcatPassword YOUR_TOMCATPASSWORD_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Name to give to each of the components. [string] +* `--tomcatUser=<tomcatUser>`: Username for tomcat manager page, if not specified tomcat will not assign users [string] +* `--tomcatPassword=<tomcatPassword>`: Tomcat manager page password, to be encrypted and included in Secret API object [string] + +### io.ksonnet.pkg.persistent-tomcat + +Deploys a stateful Tomcat server, backed by a persistent volume. Server is deployed using a Kubernetes deployment, and exposed to the network using a +service. The password is stored as a secret. + +#### Example + +```shell +# Expand prototype as a Jsonnet file, place in a file in the +# `components/` directory. (YAML and JSON are also available.) +$ ks prototype use io.ksonnet.pkg.persistent-tomcat tomcat \ + --namespace YOUR_NAMESPACE_HERE \ + --name YOUR_NAME_HERE \ + --tomcatUser YOUR_TOMCATUSER_HERE \ + --tomcatPassword YOUR_TOMCATPASSWORD_HERE +``` + +#### Parameters + +The available options to pass prototype are: + +* `--namespace=<namespace>`: Namespace in which to put the application [string] +* `--name=<name>`: Name to give to each of the components [string] +* `--tomcatUser=<tomcatUser>`: Username for tomcat manager page, if not specified tomcat will not assign users [string] +* `--tomcatPassword=<tomcatPassword>`: Tomcat manager page password, to be encrypted and included in Secret API object [string] + + +[rootReadme]: https://github.com/ksonnet/mixins diff --git a/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml b/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d3cbfc76374bb9dc842622b69c71ba6cc0ff5648 --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/examples/generated.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: tomcat-app + name: tomcat-app +spec: + template: + metadata: + labels: + app: tomcat-app + spec: + containers: + - env: + - name: TOMCAT_USERNAME + value: mockUser + - name: TOMCAT_ALLOW_REMOTE_MANAGEMENT + value: 0 + valueFrom: + secretKeyRef: + key: tomcat-password + name: mockSecretName + image: bitnami/tomcat:8.0.46-r0 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: / + port: http + initialDelaySeconds: 120 + timeoutSeconds: 5 + name: tomcat-app + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 51 + timeoutSeconds: 3 + resources: + requests: + cpu: 300m + memory: 512Mi + volumeMounts: + - mountPath: /bitnami/tomcat + name: tomcat-data + volumes: + - name: tomcat-data + persistentVolumeClaim: + claimName: mockClaimName +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + volume.alpha.kubernetes.io/storage-class: default + labels: + app: tomcat-app + name: tomcat-app + namespace: hoot-dev +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +--- +apiVersion: v1 +data: + tomcat-password: Ym9vdHM= +kind: Secret +metadata: + labels: + app: tomcat-app + name: tomcat-app + namespace: hoot-dev +type: Opaque +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: tomcat-app + name: tomcat-app + namespace: hoot-dev +spec: + ports: + - name: http + port: 80 + targetPort: http + selector: + app: tomcat-app + type: LoadBalancer diff --git a/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet b/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..26c888d6abce7d88d5396939c8c3c221bd091046 --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/examples/tomcat.jsonnet @@ -0,0 +1,12 @@ +local k = import 'k.libsonnet'; +local tc = import '../tomcat.libsonnet'; + + + +k.core.v1.list.new([ + tc.parts.deployment.persistent("hoot-dev","tomcat-app", "mockUser", "mockSecretName", "mockClaimName"), + tc.parts.pvc("hoot-dev", "tomcat-app"), + tc.parts.secret("hoot-dev", "tomcat-app", "boots"), + tc.parts.svc("hoot-dev","tomcat-app") + ]) + diff --git a/pkg/registry/testdata/incubator/tomcat/parts.yaml b/pkg/registry/testdata/incubator/tomcat/parts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1ebb96c50dca38a0d76a860e9b96e23592c0d5af --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/parts.yaml @@ -0,0 +1,42 @@ +{ + "name": "tomcat", + "apiVersion": "0.0.1", + "kind": "ksonnet.io/parts", + "description": "Apache Tomcat, or Tomcat, is an open-source web server and servlet container. This package deploys a stateless tomcat container, service, and secret to your cluster", + "author": "ksonnet team <ksonnet-help@heptio.com>", + "contributors": [ + { + "name": "Tehut Getahun", + "email": "tehut@heptio.com" + }, + { + "name": "Tamiko Terada", + "email": "tamiko@heptio.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/ksonnet/mixins" + }, + "bugs": { + "url": "https://github.com/ksonnet/mixins/issues" + }, + "keywords": [ + "tomcat", + "server", + "serverlet" + ], + "quickStart": { + "prototype": "io.ksonnet.pkg.persistent-tomcat", + "componentName": "tomcat", + "flags": { + "name": "tomcat", + "namespace": "default", + "tomcatUser": "frank", + "tomcatPassword": "boots" + }, + "comment": "Run a simple Tomcat server, backed by a PersistentVolume" + }, + "license": "Apache 2.0" +} + diff --git a/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..852d228d9b7cb1125a52f441cae48e2ea391d08a --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-persistent.jsonnet @@ -0,0 +1,27 @@ +// @apiVersion 0.1 +// @name io.ksonnet.pkg.persistent-tomcat +// @description Deploys a stateful Tomcat server, backed by a persistent volume. Server is +// deployed using a Kubernetes deployment, and exposed to the network using a +// service. The password is stored as a secret. +// @shortDescription A simple Tomcat app server, backed with persistent storage. +// @param namespace string Namespace in which to put the application +// @param name string Name to give to each of the components +// @param tomcatUser string Username for tomcat manager page, if not specified tomcat will not assign users +// @param tomcatPassword string Tomcat manager page password, to be encrypted and included in Secret API object + +local k = import 'k.libsonnet'; +local tc = import 'incubator/tomcat/tomcat.libsonnet'; + +local namespace = import 'param://namespace'; +local name = import 'param://name'; +local tomcatUser = import 'param://tomcatUser'; +local tomcatPassword = import 'param://tomcatPassword'; +local passwordSecretName = import 'param://passwordSecretName/name'; + + +k.core.v1.list.new([ + tc.parts.deployment.persistent(namespace, name, tomcatUser, passwordSecretName, name), + tc.parts.pvc(namespace, name), + tc.parts.secret(namespace, name, tomcatPassword), + tc.parts.svc(namespace,name) + ]) diff --git a/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet new file mode 100644 index 0000000000000000000000000000000000000000..491c952f2686d8a95eaed3f75a6cc54b4a99b9c9 --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/prototypes/tomcat-stateless.jsonnet @@ -0,0 +1,23 @@ +// @apiVersion 0.0.1 +// @name io.ksonnet.pkg.non-persistent-tomcat +// @description Deploys a stateless Tomcat server. Server is deployed using a Kubernetes +// deployment, and exposed to the network using a service. The password is stored as a secret. +// @shortDescription A simple, stateless Tomcat app server. +// @param namespace string Namespace in which to put the application +// @param name string Name to give to each of the components. +// @param tomcatUser string Username for tomcat manager page, if not specified tomcat will not assign users +// @param tomcatPassword string Tomcat manager page password, to be encrypted and included in Secret API object + +local k = import 'k.libsonnet'; +local tc = import 'incubator/tomcat/tomcat.libsonnet'; + +local namespace = import 'param://namespace'; +local name = import 'param://name'; +local tomcatUser = import 'param://tomcatUser'; +local tomcatPassword = import 'param://tomcatPassword'; + +k.core.v1.list.new([ + tc.parts.deployment.nonPersistent(namespace, name, tomcatUser, name), + tc.parts.secret(namespace, name, tomcatPassword), + tc.parts.svc(namespace, name) +]) diff --git a/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet b/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet new file mode 100644 index 0000000000000000000000000000000000000000..3156348d744649a639f9aac6acba9852cae71f17 --- /dev/null +++ b/pkg/registry/testdata/incubator/tomcat/tomcat.libsonnet @@ -0,0 +1,199 @@ +local k = import 'k.libsonnet'; + +{ + parts::{ + local defaults = { + serviceType: "LoadBalancer" + }, + + svc(namespace, name, selector={app: name})::{ + apiVersion: "v1", + kind: "Service", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + spec: { + type: defaults.serviceType, + ports: [ + { + name: "http", + port: 80, + targetPort: "http", + }, + ], + selector: selector + }, + }, + + secret(namespace, name, tomcatPassword):: { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name + }, + }, + type: "Opaque", + data: + if tomcatPassword != null then { + "tomcat-password": std.base64(tomcatPassword) + } else error "Please set password" + }, + + pvc(namespace, name, storageClass=null):: { + local defaults = { + persistence: { + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + }, + + kind: "PersistentVolumeClaim", + apiVersion: "v1", + metadata: { + name: name, + namespace: namespace, + labels: { + app: name, + }, + annotations: + if storageClass != null then { + "volume.beta.kubernetes.io/storage-class": storageClass, + } else { + "volume.alpha.kubernetes.io/storage-class": "default", + }, + }, + spec: { + accessModes: [ + defaults.persistence.accessMode + ], + resources: { + requests: { + storage: defaults.persistence.size, + }, + }, + }, + }, + + deployment:: { + local defaults = { + image: "bitnami/tomcat:8.0.46-r0", + imagePullPolicy: "IfNotPresent", + tomcatAllowRemoteManagement: 0, + persistence:{ + accessMode: "ReadWriteOnce", + size: "8Gi", + }, + resources:{ + requests: { + memory: "512Mi", + cpu: "300m", + }, + }, + }, + + persistent(namespace, name, tomcatUser, passwordSecretName, claimName):: + base(namespace, name, tomcatUser, passwordSecretName) + + k.apps.v1beta1.deployment.mixin.spec.template.spec.withVolumes( + { + name: "tomcat-data", + persistentVolumeClaim: { + claimName: claimName, + }, + }), + + nonPersistent(namespace, name, tomcatUser, passwordSecretName):: + base(namespace, name, tomcatUser, passwordSecretName) + + k.apps.v1beta1.deployment.mixin.spec.template.spec.withVolumes( + { + name: "tomcat-data", + emptyDir: {} + }), + + local base(namespace, name, tomcatUser, passwordSecretName) = { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { + name: name, + labels: { + app: name, + }, + }, + spec: { + template: { + metadata: { + labels: { + app: name, + }, + }, + spec: { + containers: [ + { + name: name, + image: defaults.image, + imagePullPolicy: defaults.imagePullPolicy, + env: [ + { + name: "TOMCAT_USERNAME", + value: tomcatUser, + }, + { + name: "TOMCAT_PASSWORD", + valueFrom: { + secretKeyRef: { + name: passwordSecretName, + key: "tomcat-password", + }, + }, + } + { + name: "TOMCAT_ALLOW_REMOTE_MANAGEMENT", + value: defaults.tomcatAllowRemoteManagement, + }, + ], + ports: [ + { + name: "http", + containerPort: 8080, + }, + ], + livenessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 120, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 30, + timeoutSeconds: 3, + periodSeconds: 51, + }, + resources: defaults.resources, + volumeMounts: [ + { + name: "tomcat-data", + mountPath: "/bitnami/tomcat", + }, + ], + }, + ], + }, + }, + }, + } + }, + }, +} diff --git a/pkg/util/github/github.go b/pkg/util/github/github.go index 9f11ae682de74f0df54661bc45b9c1d5e0ddf8d0..1de95d1bf31ece331c073424b0b4a2462b35fc83 100644 --- a/pkg/util/github/github.go +++ b/pkg/util/github/github.go @@ -19,6 +19,7 @@ import ( "context" "net/http" "os" + "runtime/debug" "github.com/google/go-github/github" "github.com/sirupsen/logrus" @@ -51,6 +52,8 @@ func (dg *defaultGitHub) CommitSHA1(ctx context.Context, repo Repo, refSpec stri refSpec = "master" } + debug.PrintStack() + logrus.Debugf("github: fetching SHA1 for %s/%s - %s", repo.Org, repo.Repo, refSpec) sha, _, err := dg.client().Repositories.GetCommitSHA1(ctx, repo.Org, repo.Repo, refSpec, "") return sha, err