Repository: kubernetes/kompose Branch: main Commit: a8f5d1cbdcfe Files: 552 Total size: 2.3 MB Directory structure: gitextract_lojtv245/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── enhancement.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yaml │ └── workflows/ │ ├── go.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .mention-bot ├── .openvex/ │ └── templates/ │ ├── README.md │ └── main.openvex.json ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── RELEASE.md ├── SECURITY.md ├── SECURITY_CONTACTS ├── build/ │ ├── README.md │ └── VERSION ├── client/ │ ├── client.go │ ├── convert.go │ ├── convert_test.go │ ├── options.go │ ├── options_test.go │ ├── testdata/ │ │ ├── docker-compose-profiles.yaml │ │ └── docker-compose.yaml │ └── types.go ├── cmd/ │ ├── completion.go │ ├── convert.go │ ├── root.go │ └── version.go ├── code-of-conduct.md ├── docs/ │ ├── 404.html │ ├── CNAME │ ├── Gemfile │ ├── LICENSE │ ├── README.md │ ├── _config.yml │ ├── _data/ │ │ ├── dates.yml │ │ └── menu.yml │ ├── _foobar.yml │ ├── _includes/ │ │ ├── footer.html │ │ ├── meta.html │ │ └── navbar.html │ ├── _layouts/ │ │ ├── default.html │ │ └── index.html │ ├── architecture.md │ ├── assets/ │ │ ├── css/ │ │ │ ├── animate.css │ │ │ ├── github-markdown.css │ │ │ ├── jquery.accordion.css │ │ │ ├── magnific-popup.css │ │ │ ├── owl.carousel.css │ │ │ ├── owl.theme.css │ │ │ └── style.css │ │ ├── favicons/ │ │ │ ├── browserconfig.xml │ │ │ └── site.webmanifest │ │ ├── images/ │ │ │ └── logo.xcf │ │ ├── js/ │ │ │ ├── contact.js │ │ │ ├── custom.js │ │ │ ├── jquery-2.1.1.js │ │ │ ├── jquery.accordion.js │ │ │ ├── live.js │ │ │ ├── menu-2.js │ │ │ ├── menu.js │ │ │ ├── plugins.js │ │ │ └── validator.js │ │ └── video/ │ │ ├── cat.webm │ │ └── coding.webm │ ├── conversion.md │ ├── development.md │ ├── feed.xml │ ├── getting-started.md │ ├── index.md │ ├── installation.md │ ├── integrations.md │ ├── maven-example.md │ └── user-guide.md ├── examples/ │ ├── compose.yaml │ └── web/ │ ├── Dockerfile │ ├── README.md │ ├── compose.yaml │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── public/ │ │ ├── index.html │ │ ├── script.js │ │ └── style.css │ └── vendor/ │ ├── github.com/ │ │ ├── codegangsta/ │ │ │ └── negroni/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── logger.go │ │ │ ├── negroni.go │ │ │ ├── recovery.go │ │ │ ├── response_writer.go │ │ │ ├── response_writer_pusher.go │ │ │ └── static.go │ │ ├── gomodule/ │ │ │ └── redigo/ │ │ │ ├── LICENSE │ │ │ └── redis/ │ │ │ ├── commandinfo.go │ │ │ ├── conn.go │ │ │ ├── doc.go │ │ │ ├── log.go │ │ │ ├── pool.go │ │ │ ├── pubsub.go │ │ │ ├── redis.go │ │ │ ├── reflect.go │ │ │ ├── reflect_go117.go │ │ │ ├── reflect_go118.go │ │ │ ├── reply.go │ │ │ ├── scan.go │ │ │ └── script.go │ │ ├── gorilla/ │ │ │ └── mux/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── middleware.go │ │ │ ├── mux.go │ │ │ ├── regexp.go │ │ │ ├── route.go │ │ │ └── test_helpers.go │ │ └── xyproto/ │ │ ├── pinterface/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── pinterface.go │ │ └── simpleredis/ │ │ └── v2/ │ │ ├── LICENSE │ │ ├── creator.go │ │ └── simpleredis.go │ └── modules.txt ├── go.mod ├── go.sum ├── gover.coverprofile ├── index.md ├── main.go ├── pkg/ │ ├── app/ │ │ └── app.go │ ├── kobject/ │ │ └── kobject.go │ ├── loader/ │ │ ├── compose/ │ │ │ ├── compose.go │ │ │ ├── compose_test.go │ │ │ └── utils.go │ │ └── loader.go │ ├── snap/ │ │ └── snapcraft.yaml │ ├── testutils/ │ │ ├── git.go │ │ └── kubernetes.go │ ├── transformer/ │ │ ├── kubernetes/ │ │ │ ├── k8sutils.go │ │ │ ├── k8sutils_test.go │ │ │ ├── kubernetes.go │ │ │ ├── kubernetes_test.go │ │ │ └── podspec.go │ │ ├── openshift/ │ │ │ ├── openshift.go │ │ │ ├── openshift_test.go │ │ │ └── utils.go │ │ ├── transformer.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── utils/ │ │ ├── archive/ │ │ │ └── tar.go │ │ └── docker/ │ │ ├── build.go │ │ ├── client.go │ │ ├── image.go │ │ ├── image_test.go │ │ ├── push.go │ │ └── tag.go │ └── version/ │ └── version.go └── script/ ├── check-gofmt.sh ├── manual-docs-sync.sh ├── release.sh ├── test/ │ ├── README.md │ ├── cmd/ │ │ ├── cmd_test.go │ │ ├── fix_detached_head.sh │ │ ├── globals.sh │ │ ├── lib.sh │ │ ├── tests.sh │ │ ├── tests_push_image.sh │ │ └── update-e2e.sh │ └── fixtures/ │ ├── buildargs/ │ │ ├── README.md │ │ ├── build/ │ │ │ └── Dockerfile │ │ ├── compose.yaml │ │ ├── envs │ │ └── output-os-template.json │ ├── buildconfig/ │ │ ├── build/ │ │ │ └── Dockerfile │ │ ├── compose-build-no-image.yaml │ │ ├── compose-dockerfile.yaml │ │ ├── compose-v3.yaml │ │ └── compose.yaml │ ├── change-in-volume/ │ │ ├── compose.yaml │ │ ├── output-k8s-empty-vols-template.yaml │ │ ├── output-k8s.yaml │ │ ├── output-os-empty-vols-template.yaml │ │ └── output-os.yaml │ ├── compose-env-interpolation/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── compose-env-no-interpolation/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── compose-file-env-variable/ │ │ ├── alternative-compose.yaml │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── compose-file-support/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── compose-v3.3-test/ │ │ ├── compose-config-long-warning.yaml │ │ ├── compose-config-long.yaml │ │ ├── compose-config-short-warning.yaml │ │ ├── compose-config-short.yaml │ │ ├── compose-endpoint-mode-1.yaml │ │ ├── compose-endpoint-mode-2.yaml │ │ ├── my_config.txt │ │ ├── output-k8s-config-long-warning.json │ │ ├── output-k8s-config-long.json │ │ ├── output-k8s-config-short-warning.json │ │ ├── output-k8s-config-short.json │ │ ├── output-k8s-endpoint-mode-1.json │ │ ├── output-k8s-endpoint-mode-2.json │ │ ├── output-os-config-long.json │ │ ├── output-os-config-short.json │ │ └── output-os-mode-1.json │ ├── configmap/ │ │ ├── bar.env │ │ ├── compose.yaml │ │ ├── foo.env │ │ └── output-k8s-template.json │ ├── configmap-file-configs/ │ │ ├── auth.txt │ │ ├── certs/ │ │ │ └── cert1.pem │ │ ├── certs-level1/ │ │ │ └── certs-level2/ │ │ │ └── certs-level3/ │ │ │ └── cert2.pem │ │ ├── compose-1.yaml │ │ ├── compose-2.yaml │ │ ├── compose-3.yaml │ │ ├── output-k8s-1.yaml │ │ ├── output-k8s-2.yaml │ │ ├── output-k8s-3.yaml │ │ ├── output-os-1.yaml │ │ ├── output-os-2.yaml │ │ ├── output-os-3.yaml │ │ └── users.php │ ├── configmap-pod/ │ │ ├── bar.env │ │ ├── compose.yaml │ │ ├── foo.env │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── configmap-volume/ │ │ ├── compose-withlabel.yaml │ │ ├── compose.yaml │ │ ├── output-k8s-withlabel.yaml │ │ ├── output-k8s.yaml │ │ ├── output-os-withlabel.yaml │ │ ├── output-os.yaml │ │ └── tls/ │ │ ├── a.crt │ │ └── a.key │ ├── controller/ │ │ ├── compose-controller-label-v3.yaml │ │ ├── compose-controller-label.yaml │ │ ├── compose-global.yaml │ │ ├── compose.yaml │ │ ├── output-k8s-controller-template.json │ │ ├── output-k8s-controller-v3-template.json │ │ ├── output-k8s-daemonset-template.json │ │ ├── output-k8s-deployment-template.json │ │ ├── output-k8s-global-deployment-template.json │ │ ├── output-k8s-global-template.json │ │ ├── output-k8s-rc-template.json │ │ └── output-os-controller-v3-template.json │ ├── cronjob/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── deploy/ │ │ ├── labels/ │ │ │ ├── compose.yaml │ │ │ └── output-k8s.yaml │ │ └── placement/ │ │ ├── compose-placement.yaml │ │ ├── output-placement-k8s.yaml │ │ └── output-placement-os.yaml │ ├── dockerfilepath/ │ │ └── compose.yaml │ ├── domain/ │ │ ├── compose-v3.yaml │ │ ├── compose.yaml │ │ ├── output-k8s.json │ │ └── output-os.json │ ├── entrypoint-command/ │ │ ├── compose.yaml │ │ ├── output-k8s-template.json │ │ └── output-os-template.json │ ├── env/ │ │ ├── compose.yaml │ │ ├── hadoop-hive-namenode.env │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── env-dotenv/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── env-multiple/ │ │ ├── compose.yaml │ │ ├── env1/ │ │ │ └── hadoop-hive-namenode.env │ │ ├── env2/ │ │ │ └── hadoop-hive-namenode.env │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── envfile-interpolation/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── envvars-interpolation/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── envvars-separators/ │ │ ├── compose.yaml │ │ └── output-k8s-template.json │ ├── envvars-with-status/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── etherpad/ │ │ ├── README.md │ │ ├── docker-compose-no-image.yml │ │ ├── docker-compose-no-ports.yml │ │ ├── docker-compose.yml │ │ ├── envs │ │ ├── output-k8s-template.json │ │ └── output-os-template.json │ ├── examples/ │ │ ├── output-counter-k8s.json │ │ ├── output-counter-os.json │ │ ├── output-counter-v3-k8s.json │ │ ├── output-counter-v3-os.json │ │ ├── output-gitlab-k8s.json │ │ ├── output-gitlab-os.json │ │ ├── output-k8s.json │ │ ├── output-os.json │ │ ├── output-v3-k8s.json │ │ ├── output-v3-os.json │ │ ├── output-voting-k8s.json │ │ └── output-voting-os.json │ ├── expose/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── external-traffic-policy/ │ │ ├── compose-v1.yaml │ │ ├── compose-v2.yaml │ │ ├── output-k8s-v1.yaml │ │ ├── output-k8s-v2.yaml │ │ ├── output-os-v1.yaml │ │ └── output-os-v2.yaml │ ├── fsgroup/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── gitlab/ │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── envs │ │ ├── output-k8s-template.json │ │ └── output-os-template.json │ ├── healthcheck/ │ │ ├── compose-healthcheck.yaml │ │ ├── output-healthcheck-k8s.yaml │ │ └── output-healthcheck-os.yaml │ ├── host-port-protocol/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── hpa/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── image-pull-policy/ │ │ ├── compose-files/ │ │ │ ├── v12-fail-image-pull-policy.yml │ │ │ ├── v12-image-pull-policy.yml │ │ │ └── v3-image-pull-policy.yml │ │ └── provider-files/ │ │ ├── kubernetes-v12-image-pull-policy.json │ │ └── kubernetes-v3-image-pull-policy.json │ ├── image-pull-secret/ │ │ ├── compose-files/ │ │ │ └── docker-compose-image-pull-secret.yml │ │ └── provider-files/ │ │ └── kubernetes-image-pull-secret.json │ ├── initcontainer/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── keyonly-envs/ │ │ ├── env.yml │ │ ├── envs │ │ └── output-k8s-template.json │ ├── label/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── label-port/ │ │ └── docker-compose.yml │ ├── multiple-files/ │ │ ├── first.yaml │ │ ├── output-k8s.yaml │ │ ├── output-os.yaml │ │ └── second.yaml │ ├── multiple-type-volumes/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ ├── output-os.yaml │ │ └── tls/ │ │ ├── a.crt │ │ └── a.key │ ├── namespace/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── network/ │ │ ├── compose-v3.yaml │ │ ├── output-k8s.json │ │ └── output-os.json │ ├── network-mode-service/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── network-policies/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── nginx-node-redis/ │ │ ├── README.md │ │ ├── compose-v3.yaml │ │ ├── compose.yaml │ │ ├── nginx/ │ │ │ ├── Dockerfile │ │ │ └── nginx.conf │ │ ├── node/ │ │ │ ├── Dockerfile │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── test/ │ │ │ └── dummyTest.js │ │ ├── output-k8s-template-v3.json │ │ ├── output-k8s-template.json │ │ ├── output-os-template-v3.json │ │ ├── output-os-template.json │ │ └── output-os.json │ ├── no-profile-warning/ │ │ └── compose.yaml │ ├── ports-with-ip/ │ │ ├── docker-compose.yml │ │ ├── output-k8s-template.json │ │ └── output-k8s.json │ ├── pvc-request-size/ │ │ ├── compose.yaml │ │ ├── output-k8s.json │ │ └── output-os.json │ ├── read-only/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── redis-example/ │ │ └── compose.yaml │ ├── resources-lowercase/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── secrets/ │ │ ├── docker-compose-secrets-long.yml │ │ ├── docker-compose-secrets-short.yml │ │ ├── my_secret.txt │ │ ├── output-long-k8s.json │ │ ├── output-long-os.json │ │ ├── output-short-k8s.json │ │ └── output-short-os.json │ ├── security-contexts/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── service-group/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── service-label/ │ │ ├── docker-compose.yaml │ │ ├── output-k8s.json │ │ └── output-oc.json │ ├── service-name-change/ │ │ ├── docker-compose.yml │ │ ├── output-k8s-template.json │ │ └── output-os-template.json │ ├── single-file-output/ │ │ ├── compose.yaml │ │ └── output-k8s.yaml │ ├── statefulset/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── stdin/ │ │ ├── docker-compose.yaml │ │ ├── output-k8s.json │ │ └── output.json │ ├── stdin-true/ │ │ ├── docker-compose.yml │ │ ├── output-k8s-template.json │ │ └── output-os-template.json │ ├── storage-class-name/ │ │ ├── compose.yaml │ │ ├── output-k8s.json │ │ └── output-os.json │ ├── tty-true/ │ │ ├── docker-compose.yml │ │ ├── output-k8s-template.json │ │ ├── output-oc.json │ │ └── output-os-template.json │ ├── unused/ │ │ ├── expose-service/ │ │ │ ├── compose-files/ │ │ │ │ ├── docker-compose-expose-hostname-multiple-ports.yml │ │ │ │ ├── docker-compose-expose-hostname-tls.yml │ │ │ │ ├── docker-compose-expose-hostname.yml │ │ │ │ ├── docker-compose-expose-multiple-hostname-tls.yml │ │ │ │ ├── docker-compose-expose-multiple-hostname.yml │ │ │ │ ├── docker-compose-expose-true-multiple-ports.yml │ │ │ │ └── docker-compose-expose-true.yml │ │ │ └── provider-files/ │ │ │ ├── kubernetes-expose-hostname-multiple-ports.json │ │ │ ├── kubernetes-expose-hostname-tls.json │ │ │ ├── kubernetes-expose-hostname.json │ │ │ ├── kubernetes-expose-multiple-hostname-tls.json │ │ │ ├── kubernetes-expose-multiple-hostname.json │ │ │ ├── kubernetes-expose-true-multiple-ports.json │ │ │ ├── kubernetes-expose-true.json │ │ │ ├── openshift-expose-hostname-multiple-ports.json │ │ │ ├── openshift-expose-hostname.json │ │ │ ├── openshift-expose-true-multiple-ports.json │ │ │ └── openshift-expose-true.json │ │ ├── merge-multiple-compose/ │ │ │ ├── base.yml │ │ │ ├── compose-new-service-prob.yml │ │ │ ├── compose-port-base.yml │ │ │ ├── compose-port-prod.yml │ │ │ ├── dev.yml │ │ │ ├── first_config.txt │ │ │ ├── other-toplevel-base.yml │ │ │ ├── other-toplevel-dev.yml │ │ │ ├── other-toplevel-ext.yml │ │ │ ├── other-toplevel-override.yml │ │ │ ├── output-base-template.json │ │ │ ├── output-compose-new-service-template.json │ │ │ ├── output-compose-port-template.json │ │ │ ├── output-openshift-template.json │ │ │ ├── output-other-toplevel-merge-template.json │ │ │ ├── output-other-toplevel-override-template.json │ │ │ ├── output-service-merge-concat-template.json │ │ │ ├── second_config.txt │ │ │ ├── service-merge-concat-base.yml │ │ │ └── service-merge-concat-override.yml │ │ └── v3/ │ │ ├── docker-compose-3.5.yaml │ │ ├── docker-compose-deploy.yaml │ │ ├── docker-compose-env-subs.yaml │ │ ├── docker-compose-env.yaml │ │ ├── docker-compose-full-example.yaml │ │ ├── docker-compose-memcpu-partial.yaml │ │ ├── docker-compose-memcpu.yaml │ │ ├── docker-compose-unset-env.yaml │ │ ├── docker-compose-volumes.yaml │ │ ├── docker-compose.yaml │ │ ├── example1.env │ │ ├── example2.env │ │ ├── output-deploy-k8s.json │ │ ├── output-deploy-os.json │ │ ├── output-env-k8s.json │ │ ├── output-env-subs.json │ │ ├── output-k8s-3.5.json │ │ ├── output-k8s-full-example-template.json │ │ ├── output-k8s-full-example.json │ │ ├── output-k8s-template.json │ │ ├── output-memcpu-k8s.json │ │ ├── output-memcpu-partial-k8s.json │ │ ├── output-os-full-example.json │ │ ├── output-os-template.json │ │ ├── output-unset-env-k8s.json │ │ └── output-volumes-k8s-template.json │ ├── v2/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── v3.0/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── vols-subpath/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ ├── volume-mounts/ │ │ ├── hostpath/ │ │ │ ├── docker-compose-v3.yml │ │ │ ├── docker-compose.yml │ │ │ ├── output-k8s-template.json │ │ │ └── output-os-template.json │ │ ├── named-volume/ │ │ │ ├── docker-compose-v3.yml │ │ │ ├── docker-compose.yml │ │ │ ├── output-k8s-template.json │ │ │ ├── output-k8s-v3.json │ │ │ └── output-os-template.json │ │ ├── simple-vol-mounts/ │ │ │ ├── docker-compose.yml │ │ │ ├── output-k8s-template.json │ │ │ └── output-os-template.json │ │ ├── tmpfs/ │ │ │ ├── docker-compose.yml │ │ │ ├── output-k8s-template.json │ │ │ └── output-os-template.json │ │ ├── volumes-from/ │ │ │ ├── docker-compose-case.yml │ │ │ ├── docker-compose.yml │ │ │ ├── output-k8s-case.json │ │ │ ├── output-k8s-template.json │ │ │ ├── output-os-case.json │ │ │ └── output-os-template.json │ │ └── windows/ │ │ ├── compose.yaml │ │ ├── output-k8s.yaml │ │ └── output-os.yaml │ └── yaml-and-yml/ │ ├── docker-compose.yaml │ ├── output-k8s-template.json │ ├── output-os-template.json │ └── yml/ │ ├── docker-compose.yml │ ├── output-k8s-template.json │ └── output-os-template.json └── test_in_container/ ├── Dockerfile └── run.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug description: File a bug/issue title: "[BUG] " labels: ["kind/bug"] body: - type: markdown attributes: value: "## Thank you for contributing to our Kompose!" - type: textarea attributes: label: Expected Behavior description: | Briefly describe what is the desired behavior. validations: required: true - type: textarea attributes: label: Actual Behavior description: | Briefly describe what is the actual behavior. validations: required: true - type: textarea attributes: label: Steps To Reproduce description: Steps to reproduce the behavior. placeholder: | 1. In this environment... 2. With this config... 3. Run '...' 4. See error or unexpected result... validations: required: false - type: textarea attributes: label: Kompose Version description: | Paste output of `kompose version`. render: Text validations: required: true - type: textarea attributes: label: Docker-Compose file description: Paste output of the `docker-compose.yaml` that you are using. render: YAML validations: required: false - type: textarea attributes: label: Anything else? description: | Links? References? Anything that will give us more context about the issue you are encountering! Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Kubernetes Community Slack url: https://kubernetes.slack.com about: "Use the #kompose channel" ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement.yml ================================================ name: Enhancement Tracking Issue description: Provide supporting details for a feature in development labels: kind/feature body: - type: markdown attributes: value: "## Thank you for contributing to our Kompose!" - type: textarea id: feature attributes: label: What would you like to be added? description: | Describe what feature/enhancement that you want to be added to Kompose. validations: required: true - type: textarea id: rationale attributes: label: Why is this needed? validations: required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ #### What type of PR is this? <!-- Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature --> #### What this PR does / why we need it: #### Which issue(s) this PR fixes: <!-- *Automatically closes linked issue when PR is merged. Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Fixes # #### Special notes for your reviewer: ================================================ FILE: .github/dependabot.yaml ================================================ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - commit-message: include: "scope" prefix: "chore(deps)" directory: "/" open-pull-requests-limit: 10 package-ecosystem: "gomod" schedule: interval: "daily" - commit-message: include: "scope" prefix: "chore(ci)" directory: "/" open-pull-requests-limit: 10 package-ecosystem: "github-actions" schedule: interval: "daily" ================================================ FILE: .github/workflows/go.yml ================================================ name: Go on: push: branches: [ main ] pull_request: branches: [ main ] env: # Avoid noisy outputs like "tput: No value for $TERM and no -T specified" TERM: dumb jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v5 with: go-version: ^1.21 id: go - name: Check out code into the Go module directory uses: actions/checkout@v6 - name: Build run: make bin - name: Upload a Build Artifact uses: actions/upload-artifact@v6 with: name: "kompose" path: "kompose" ================================================ FILE: .github/workflows/lint.yml ================================================ name: lint on: pull_request: jobs: lint: strategy: matrix: go: [1.21, 1.22] name: lint runs-on: ubuntu-latest steps: - name: "Checkout" uses: actions/checkout@v6 - name: "Install golang" uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: "Run go vet" run: "go vet ./pkg/..." ================================================ FILE: .github/workflows/test.yml ================================================ name: Kompose CI on: push: branches: - main pull_request: env: # Avoid noisy outputs like "tput: No value for $TERM and no -T specified" TERM: dumb jobs: test: name: Test with ${{ matrix.go }} and CROSS_COMPILE=${{ matrix.cross_compile }} runs-on: ubuntu-latest strategy: matrix: go: [1.21, 1.22] cross_compile: [true, false] steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Install dyff run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8 - name: Run tests run: make test - name: Perform cross compile if: ${{ matrix.cross_compile }} run: make cross docs: name: Build docs and Coveralls integration runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: ^1.21 - name: Install dyff run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8 - name: Create .coverprofile for each targeted directory by re:running tests run: make test - name: Collect all .coverprofile files and save it to one file gover.coverprofile run: gover - name: Send coverage run: goveralls -coverprofile=gover.coverprofile -service=github env: # As per https://github.com/mattn/goveralls#github-actions COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ # # KOMPOSE SPECIFIC # # Ignore compiled Kompose files kompose bin /docker-compose.yaml /docker-compose.yml /compose.yaml /compose.yml changes.txt # Ignore site documents / when switching branches docs/_site/ docs/.jekyll-cache/ docs/.git/ docs/.gitignore _site/ .jekyll-cache/ # # GO SPECIFIC # # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # Output of the go coverage tool, specifically when used with LiteIDE *.out # Coveralls files .coverprofile # # VIM SPECIFIC # # swap [._]*.s[a-w][a-z] [._]s[a-w][a-z] # session Session.vim # temporary .netrwhist *~ # auto-generated tag files tags # IntelliJ IDE specific .idea .DS_Store # VSCode specific .vscode # Client Test generated files client/testdata/generated pkg/mod ================================================ FILE: .gitmodules ================================================ ================================================ FILE: .golangci.yml ================================================ # Golang CI pipeline configuration linters: disable-all: true # Run golangci-lint.yml linters to see the list of all linters # Please keep them sorted alphabetically enable: - bodyclose - deadcode - depguard # - dogsled # - errcheck # - goconst - goimports # - staticcheck - goprintffuncname # - gosimple - govet # - ineffassign - misspell # - nakedret - nolintlint - rowserrcheck - structcheck - stylecheck - typecheck - unconvert - varcheck - whitespace ================================================ FILE: .mention-bot ================================================ { "maxReviewers": 2, "numFilesToCheck": 5, "message": "@pullRequester, thank you for the pull request! We'll request some people to review your PR. @reviewers, please review this.", "fileBlacklist": ["*.md"], "userBlacklist": ["ngtuna", "janetkuo", "sebgoa", "dustymabe", "gitlawr"], "actions": ["opened", "labeled"], "skipAlreadyMentionedPR": true, "createReviewRequest": true } ================================================ FILE: .openvex/templates/README.md ================================================ # OpenVEX Templates Directory This directory contains the OpenVEX data for this repository. The files stored in this directory are used as templates by `vexctl generate` when generating VEX data for a release or a specific artifact. To add new statements to publish data about a vulnerability, download [vexctl](https://github.com/openvex/vexctl) and append new statements using `vexctl add`. For example: ``` vexctl add --in-place main.openvex.json pkg:oci/test CVE-2014-1234567 fixed ``` That will add a new VEX statement expressing that the impact of CVE-2014-1234567 is under investigation in the test image. When cutting a new release, for `pkg:oci/test` the new file will be incorporated to the relase's VEX data. ## Read more about OpenVEX To know more about generating, publishing and using VEX data in your project, please check out the vexctl repository and documentation: https://github.com/openvex/vexctl OpenVEX also has an examples repository with samples and docs: https://github.com/openvex/examples ================================================ FILE: .openvex/templates/main.openvex.json ================================================ { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://openvex.dev/docs/public/vex-6f9001fd8630edd2996df09f345882066d7b5bf512e54af918343d278640ecd0", "author": "vexctl (automated template)", "timestamp": "2023-12-15T19:10:43.910365Z", "version": 1, "statements": [] } ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-added-large-files - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: - id: go-fmt - id: go-imports - id: golangci-lint - id: go-unit-tests - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook rev: v9.18.0 hooks: - id: commitlint stages: [commit-msg] additional_dependencies: ["@commitlint/config-conventional"] ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing guidelines ## How to become a contributor and submit your own code ### Contributor License Agreements We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles. Please fill out either the individual or corporate Contributor License Agreement (CLA). - If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an individual CLA. - If you work for a company that wants to allow you to contribute your work, then you'll need to sign a corporate CLA. Contact one of the [OWNERS](OWNERS) on Slack to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. ### Contributing A Patch 1. Submit an issue describing your proposed change to the repo in question. 2. The [repo owners](OWNERS) will respond to your issue promptly. 3. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 4. Fork the desired repo, develop and test your code changes. 5. Submit a pull request. Note: Code-related PR's require one ACK / LGTM from a maintainer or core contributor. Doc-related PR's require either one or none depending on the content changed (for example, a spec change would require one, but a spelling error would require none). ### Adding dependencies If your patch depends on new packages, make sure that both `go.mod` and `go.sum` are updated properly. Also we recommend you to execute `go mod tidy` before sending a pull request. ================================================ FILE: Dockerfile ================================================ # Alpine Builder FROM alpine AS builder RUN apk add --no-cache curl COPY ./build/VERSION VERSION RUN \ version=$(cat VERSION) && \ ARCH=$(uname -m | sed 's/armv7l/arm/g' | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') && \ curl -L \ "https://github.com/kubernetes/kompose/releases/download/v${version}/kompose-linux-${ARCH}" \ -o kompose && \ chmod +x kompose # Runtime FROM alpine COPY --from=builder /kompose /usr/bin/kompose ================================================ FILE: Jenkinsfile ================================================ @Library('github.com/fabric8io/fabric8-pipeline-library@master') def dummy goTemplate{ dockerNode{ goMake{ githubOrganisation = 'kubernetes' dockerOrganisation = 'fabric8' project = 'kompose' makeCommand = "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/usr/local/:/go/bin:/home/jenkins/go/bin \ && bash script/test/cmd/fix_detached_head.sh && make test" } } } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: Makefile ================================================ # Copyright 2016 The Kubernetes 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. GITCOMMIT := $(shell git rev-parse --short HEAD) BUILD_FLAGS := -ldflags="-w -s -X github.com/kubernetes/kompose/pkg/version.GITCOMMIT=$(GITCOMMIT)" TEST_IMAGE := kompose/tests:latest # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool @[ -f $(1) ] || { \ set -e ;\ TMP_DIR=$$(mktemp -d) ;\ cd $$TMP_DIR ;\ go mod init tmp ;\ echo "Downloading $(2)" ;\ GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ rm -rf $$TMP_DIR ;\ } endef default: bin .PHONY: all all: bin .PHONY: bin bin: CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -o kompose main.go .PHONY: install install: go install ${BUILD_FLAGS} # kompile kompose for multiple platforms .PHONY: cross cross: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-amd64" main.go GOOS=linux GOARCH=arm CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm" main.go GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm64" main.go GOOS=windows GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-windows-amd64.exe" main.go GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-amd64" main.go GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-arm64" main.go .PHONY: clean clean: rm -f kompose rm -r -f bundles .PHONY: test-unit test-unit: go test -short $(BUILD_FLAGS) -race -cover -v ./... # Run unit tests and collect coverage .PHONY: test-unit-cover test-unit-cover: # First install packages that are dependencies of the test. go test -short -i -race -cover ./... # go test doesn't support colleting coverage across multiple packages, # generate go test commands using go list and run go test for every package separately go list -f '"go test -short -race -cover -v -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"' github.com/kubernetes/kompose/... | grep -v "vendor" | xargs -L 1 -P4 sh -c # run openshift up/down tests .PHONY: test-openshift test-openshift: ./script/test_in_openshift/run.sh # run commandline tests .PHONY: test-cmd test-cmd: ./script/test/cmd/tests.sh # run all validation tests .PHONY: validate validate: gofmt vet .PHONY: vet vet: go vet ./pkg/... .PHONY: gofmt gofmt: ./script/check-gofmt.sh # Run all tests .PHONY: test test: bin test-dep validate test-unit-cover install test-cmd # Install all the required test-dependencies before executing tests (only valid when running `make test`) .PHONY: test-dep test-dep: go install github.com/mattn/goveralls@latest go install github.com/modocache/gover@latest go install github.com/mitchellh/gox@latest # build docker image that is used for running all test locally .PHONY: test-image test-image: docker build -t $(TEST_IMAGE) -f script/test_in_container/Dockerfile script/test_in_container/ # run all test locally in docker image (image can be build by by build-test-image target) .PHONY: test-container test-container: docker run -v `pwd`:/opt/tmp/kompose:ro -it $(TEST_IMAGE) .PHONY: test-k8s test-k8s: ./script/test_k8s/test.sh GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint .PHONY: install-golangci-lint install-golangci-lint: # golangci-lint version must consistent with github CI # ref: ./.github/workflows/golangci-lint.yml $(call go-get-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2) .PHONY: golangci-lint golangci-lint: install-golangci-lint $(GOLANGCI_LINT) run -c .golangci.yml --timeout 5m .PHONY: test-client test-client: go test ./client/... ================================================ FILE: OWNERS ================================================ # See the OWNERS file documentation: # https://github.com/kubernetes/community/blob/master/contributors/devel/owners.md reviewers: - cdrage - hangyan - TessaIO approvers: - cdrage - hangyan - TessaIO ================================================ FILE: README.md ================================================ # Kompose (Kubernetes + Compose) [![Build Status Widget]][Build Status] [![Coverage Status Widget]][Coverage Status] [![GoDoc Widget]][GoDoc] [![GoReportCard Widget]][GoReportCardResult] ![logo](/docs/assets/images/logo.png) `kompose` is a tool to help users who are familiar with `docker-compose` move to [Kubernetes](http://kubernetes.io). `kompose` takes a [Compose Specification](https://compose-spec.io/) file and translates it into Kubernetes resources. `kompose` is a convenience tool to go from local Compose environment to managing your application with Kubernetes. Transformation of the [Compose Specification](https://compose-spec.io/) format to Kubernetes resources manifest may not be exact, but it helps tremendously when first deploying an application on Kubernetes. ## Use Case Convert [`compose.yaml`](https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml) into Kubernetes deployments and services with one simple command: ```sh $ kompose convert -f compose.yaml INFO Kubernetes file "frontend-service.yaml" created INFO Kubernetes file "redis-leader-service.yaml" created INFO Kubernetes file "redis-replica-service.yaml" created INFO Kubernetes file "frontend-deployment.yaml" created INFO Kubernetes file "redis-leader-deployment.yaml" created INFO Kubernetes file "redis-replica-deployment.yaml" created ``` Other examples are provided in the _examples_ [directory](./examples). ## Installation We have multiple ways to install Kompose. Our preferred method is downloading the binary from the latest GitHub release. Our entire list of installation methods are located in our [installation.md](/docs/installation.md) document. Installation methods: - [Binary (Preferred method)](/docs/installation.md#github-release) - [Go](/docs/installation.md#go) - [CentOS](/docs/installation.md#centos) - [openSUSE/SLE](/docs/installation.md#opensusesle) - [NixOS](/docs/installation.md#nixos) - [macOS (Homebrew and MacPorts)](/docs/installation.md#macos) - [Windows](/docs/installation.md#windows) - [Docker](/docs/installation.md#docker) #### Binary installation Kompose is released via GitHub on a three-week cycle, you can see all current releases on the [GitHub release page](https://github.com/kubernetes/kompose/releases). **Linux and macOS:** ```sh # Linux curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-linux-amd64 -o kompose # macOS curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-darwin-amd64 -o kompose chmod +x kompose sudo mv ./kompose /usr/local/bin/kompose ``` **Windows:** Download from [GitHub](https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-windows-amd64.exe) and add the binary to your PATH. ## Shell autocompletion We support Bash, Zsh and Fish autocompletion. ```sh # Bash (add to .bashrc for persistence) source <(kompose completion bash) # Zsh (add to .zshrc for persistence) source <(kompose completion zsh) # Fish autocompletion kompose completion fish | source ``` ## Development and building of Kompose ### Building with `go` **Requisites:** 1. [make](https://www.gnu.org/software/make/) 2. [Golang](https://golang.org/) v1.6 or later 3. Set `GOPATH` correctly or click [SettingGOPATH](https://github.com/golang/go/wiki/SettingGOPATH) for details **Steps:** 1. Clone repository ```console $ git clone https://github.com/kubernetes/kompose.git $GOPATH/src/github.com/kubernetes/kompose ``` 2. Change directory to the cloned repo. ```console cd $GOPATH/src/github.com/kubernetes/kompose ``` 3. Build with `make` ```console $ make bin ``` 4. Or build with `go` ```console $ go build -o kompose main.go ``` 5. Test your changes ```console $ make test ``` ## Documentation Documentation can be found at our [kompose.io](http://kompose.io) website or our [docs](https://github.com/kubernetes/kompose/tree/main/docs) folder. Here is a list of all available docs: - [Quick start](docs/getting-started.md) - [Installation](docs/installation.md) - [User guide](docs/user-guide.md) - [Conversion](docs/conversion.md) - [Architecture](docs/architecture.md) - [Development](docs/development.md) ## Community, Discussion, Contribution, and Support **Issues:** If you find any issues, please [file it](https://github.com/kubernetes/kompose/issues). **Kubernetes Community:** As part of the Kubernetes ecosystem, we follow the Kubernetes community principles. More information can be found on the [community page](http://kubernetes.io/community/). **Chat (Slack):** We're fairly active on [Slack](http://slack.kubernetes.io#kompose) and you can find us in the #kompose channel. ### Code of Conduct Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). [Build Status]: https://github.com/kubernetes/kompose/actions?query=workflow%3A%22Kompose+CI%22 [Build Status Widget]: https://github.com/kubernetes/kompose/workflows/Kompose%20CI/badge.svg [GoDoc]: https://godoc.org/github.com/kubernetes/kompose [GoDoc Widget]: https://godoc.org/github.com/kubernetes/kompose?status.svg [Coverage Status Widget]: https://coveralls.io/repos/github/kubernetes/kompose/badge.svg?branch=main [Coverage Status]: https://coveralls.io/github/kubernetes/kompose?branch=main [GoReportCard Widget]: https://goreportcard.com/badge/github.com/kubernetes/kompose [GoReportCardResult]: https://goreportcard.com/report/github.com/kubernetes/kompose ================================================ FILE: RELEASE.md ================================================ # Release Process The process is as follows: 1. A PR proposing a new release with a changelog since the last release 1. At least 2 or more [OWNERS](OWNERS) must LGTM this release 1. The release PR is closed 1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION` ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Security Announcements Join the [kubernetes-security-announce] group for security and vulnerability announcements. You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss]. ## Reporting a Vulnerability Instructions for reporting a vulnerability can be found on the [Kubernetes Security and Disclosure Information] page. ## Supported Versions Information about supported Kubernetes versions can be found on the [Kubernetes version and version skew support policy] page on the Kubernetes website. [kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce [kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50 [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability ================================================ FILE: SECURITY_CONTACTS ================================================ # Defined below are the security contacts for this repo. # # They are the contact point for the Product Security Committee to reach out # to for triaging and handling of incoming issues. # # The below names agree to abide by the # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) # and will be removed and replaced if they violate that agreement. # # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # INSTRUCTIONS AT https://kubernetes.io/security/ cdrage kadel hangyan janetkuo ngtuna sebgoa AhmedGrati ================================================ FILE: build/README.md ================================================ # Fedora RPM packaging There are instructions on how to build the RPM. # 1. Gofed Grab gofed from https://github.com/gofed/gofed Choose which version of the repo you want to build. For kompose it was 0.3.0 and the commit was 135165b39c55d29a5426479ded81eddd56bfbaf4 Run the following to generate spec file: ```sh gofed repo2spec --detect github.com/kubernetes/kompose --commit 135165b39c55d29a5426479ded81eddd56bfbaf4 --with-extra --with-build -f ``` The spec file is now located at: ```sh $HOME/gofed/golang-github-kubernetes-incubator-kompose/golang-github-kubernetes-incubator-kompose.spec ``` # 2. Dependencies Now we need to go through and fix some things. Generate bundled dependencies by using parsedeps.go Go to the kompose source folder and then run: ```sh go run parsedeps.go ``` In the future this will possibly done by `gofed`, see: https://github.com/gofed/gofed/issues/42 # 3. Building a source RPM locally on CentOS First, follow instructions to do local setup https://wiki.centos.org/HowTos/SetupRpmBuildEnvironment Source: https://wiki.centos.org/HowTos/RebuildSRPM Second, checkout the source to the release commit Copy the kompose code directory with name `kompose-135165b39c55d29a5426479ded81eddd56bfbaf4` Tar the `kompose-135165b39c55d29a5426479ded81eddd56bfbaf4` code directory as `kompose-135165b.tar.gz` and copy it to `$HOME/rpmbuild/SOURCES/` Run following command: ```sh rpmbuild -ba kompose.spec ``` Find the srpm in `$HOME/rpmbuild/SRPMS` Find the RPM in `$HOME/rpmbuild/RPM/arch/` Check that the dependencies are proper: ```sh rpm -qpR RPMS/x86_64/kompose-0.3.0-0.1.git135165b.el7.centos.x86_64.rpm ``` # 4. Running in on Koji (build system) First, setup your environment in order to run Koji: https://fedoraproject.org/wiki/Using_the_Koji_build_system Example setup: ```sh fedora-packager-setup kinit <username>@FEDORAPROJECT.ORG ``` To build it on koji run: ``` koji build --scratch rawhide kompose-0.3.0-0.1.git135165b.el7.centos.src.rpm ``` ================================================ FILE: build/VERSION ================================================ 1.38.0 ================================================ FILE: client/client.go ================================================ package client type Kompose struct { suppressWarnings bool verbose bool errorOnWarning bool } func NewClient(opts ...Opt) (*Kompose, error) { k := &Kompose{ suppressWarnings: false, verbose: false, errorOnWarning: false, } for _, op := range opts { if err := op(k); err != nil { return nil, err } } return k, nil } ================================================ FILE: client/convert.go ================================================ package client import ( "fmt" "github.com/kubernetes/kompose/pkg/app" "github.com/kubernetes/kompose/pkg/kobject" "k8s.io/apimachinery/pkg/runtime" ) func (k *Kompose) Convert(options ConvertOptions) ([]runtime.Object, error) { options = k.setDefaultValues(options) err := k.validateOptions(options) if err != nil { return nil, err } kobjectConvertOptions := kobject.ConvertOptions{ ToStdout: options.ToStdout, CreateChart: k.createChart(options), GenerateYaml: true, GenerateJSON: options.GenerateJson, Replicas: *options.Replicas, InputFiles: options.InputFiles, OutFile: options.OutFile, Provider: k.getProvider(options), CreateD: k.createDeployment(options), CreateDS: k.createDaemonSet(options), CreateRC: k.createReplicationController(options), Build: *options.Build, BuildRepo: k.buildRepo(options), BuildBranch: k.buildBranch(options), PushImage: options.PushImage, PushImageRegistry: options.PushImageRegistry, CreateDeploymentConfig: k.createDeploymentConfig(options), EmptyVols: false, Profiles: options.Profiles, Volumes: *options.VolumeType, PVCRequestSize: options.PvcRequestSize, InsecureRepository: k.insecureRepository(options), IsDeploymentFlag: k.createDeployment(options), IsDaemonSetFlag: k.createDaemonSet(options), IsReplicationControllerFlag: k.createReplicationController(options), Controller: k.getController(options), IsReplicaSetFlag: *options.Replicas != 0, IsDeploymentConfigFlag: k.createDeploymentConfig(options), YAMLIndent: 2, WithKomposeAnnotation: *options.WithKomposeAnnotations, MultipleContainerMode: k.multiContainerMode(options), ServiceGroupMode: k.serviceGroupMode(options), ServiceGroupName: k.serviceGroupName(options), SecretsAsFiles: k.secretsAsFiles(options), GenerateNetworkPolicies: options.GenerateNetworkPolicies, } err = app.ValidateComposeFile(&kobjectConvertOptions) if err != nil { return nil, err } objects, err := app.Convert(kobjectConvertOptions) return objects, err } func (k *Kompose) setDefaultValues(options ConvertOptions) ConvertOptions { replicasDefaultValue := 1 buildDefaultValue := "none" volumeTypeDefaultValue := "persistentVolumeClaim" withKomposeAnnotationsDefaultValue := true kubernetesControllerDefaultValue := "" kubernetesServiceGroupModeDefaultValue := "" if options.Replicas == nil { options.Replicas = &replicasDefaultValue } if options.Build == nil { options.Build = &buildDefaultValue } if options.VolumeType == nil { options.VolumeType = &volumeTypeDefaultValue } if options.WithKomposeAnnotations == nil { options.WithKomposeAnnotations = &withKomposeAnnotationsDefaultValue } if options.Provider == nil { options.Provider = Kubernetes{ Controller: &kubernetesControllerDefaultValue, } } if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { if kubernetesProvider.Controller == nil { options.Provider = Kubernetes{ Controller: &kubernetesControllerDefaultValue, Chart: options.Provider.(Kubernetes).Chart, MultiContainerMode: options.Provider.(Kubernetes).MultiContainerMode, ServiceGroupMode: options.Provider.(Kubernetes).ServiceGroupMode, ServiceGroupName: options.Provider.(Kubernetes).ServiceGroupName, SecretsAsFiles: options.Provider.(Kubernetes).SecretsAsFiles, } } if kubernetesProvider.ServiceGroupMode == nil { options.Provider = Kubernetes{ Controller: options.Provider.(Kubernetes).Controller, Chart: options.Provider.(Kubernetes).Chart, MultiContainerMode: options.Provider.(Kubernetes).MultiContainerMode, ServiceGroupMode: &kubernetesServiceGroupModeDefaultValue, ServiceGroupName: options.Provider.(Kubernetes).ServiceGroupName, SecretsAsFiles: options.Provider.(Kubernetes).SecretsAsFiles, } } } return options } func (k *Kompose) validateOptions(options ConvertOptions) error { build := options.Build if *build != string(LOCAL) && *build != string(BUILD_CONFIG) && *build != string(NONE) { return fmt.Errorf( "unexpected Value for Build field. Possible values are: %v, %v, and %v", string(LOCAL), string(BUILD_CONFIG), string(NONE), ) } volumeType := options.VolumeType if *volumeType != string(PVC) && *volumeType != string(EMPTYDIR) && *volumeType != string(HOSTPATH) && *volumeType != string(CONFIGMAP) { return fmt.Errorf( "unexpected Value for VolumeType field. Possible values are: %v, %v, %v, %v", string(PVC), string(EMPTYDIR), string(HOSTPATH), string(CONFIGMAP), ) } if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { kubernetesController := kubernetesProvider.Controller if *kubernetesController != "" && *kubernetesController != string(DEPLOYMENT) && *kubernetesController != string(DAEMONSET) && *kubernetesController != string(REPLICATION_CONTROLLER) { return fmt.Errorf( "unexpected Value for Kubernetes Controller field. Possible values are: %v, %v, and %v", string(DEPLOYMENT), string(DAEMONSET), string(REPLICATION_CONTROLLER), ) } kubernetesServiceGroupMode := kubernetesProvider.ServiceGroupMode if *kubernetesServiceGroupMode != string(LABEL) && *kubernetesServiceGroupMode != string(VOLUME) && *kubernetesServiceGroupMode != "" { return fmt.Errorf( "unexpected Value for Kubernetes Service Groupe Mode field. Possible values are: %v, %v, ''", string(LABEL), string(VOLUME), ) } if *build == string(BUILD_CONFIG) { return fmt.Errorf("the build value %v is only supported for Openshift provider", string(BUILD_CONFIG)) } } return nil } func (k *Kompose) createDeployment(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return *kubernetesProvider.Controller == string(DEPLOYMENT) } return false } func (k *Kompose) createDaemonSet(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return *kubernetesProvider.Controller == string(DAEMONSET) } return false } func (k *Kompose) createReplicationController(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return *kubernetesProvider.Controller == string(REPLICATION_CONTROLLER) } return false } func (k *Kompose) createChart(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return kubernetesProvider.Chart } return false } func (k *Kompose) multiContainerMode(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return kubernetesProvider.MultiContainerMode } return false } func (k *Kompose) serviceGroupMode(options ConvertOptions) string { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return *kubernetesProvider.ServiceGroupMode } return "" } func (k *Kompose) serviceGroupName(options ConvertOptions) string { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return kubernetesProvider.ServiceGroupName } return "" } func (k *Kompose) secretsAsFiles(options ConvertOptions) bool { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return kubernetesProvider.SecretsAsFiles } return false } func (k *Kompose) createDeploymentConfig(options ConvertOptions) bool { if _, ok := options.Provider.(Openshift); ok { return true } return false } func (k *Kompose) insecureRepository(options ConvertOptions) bool { if openshiftProvider, ok := options.Provider.(Openshift); ok { return openshiftProvider.InsecureRepository } return false } func (k *Kompose) buildRepo(options ConvertOptions) string { if openshiftProvider, ok := options.Provider.(Openshift); ok { return openshiftProvider.BuildRepo } return "" } func (k *Kompose) buildBranch(options ConvertOptions) string { if openshiftProvider, ok := options.Provider.(Openshift); ok { return openshiftProvider.BuildRepo } return "" } func (k *Kompose) getProvider(options ConvertOptions) string { if _, ok := options.Provider.(Openshift); ok { return "openshift" } if _, ok := options.Provider.(Kubernetes); ok { return "kubernetes" } return "kubernetes" } func (k *Kompose) getController(options ConvertOptions) string { if kubernetesProvider, ok := options.Provider.(Kubernetes); ok { return *kubernetesProvider.Controller } return "" } ================================================ FILE: client/convert_test.go ================================================ package client import ( "fmt" v1 "k8s.io/api/core/v1" "sort" "testing" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" appsv1 "k8s.io/api/apps/v1" ) func TestConvertError(t *testing.T) { randomBuildValue := "random-build" randomVolumeTypeValue := "random-volume-type" randomKubernetesControllerValue := "random-controller" randomKubernetesServiceGroupModeValue := "random-group-mode" buildConfigValue := string(BUILD_CONFIG) testCases := []struct { options ConvertOptions errorMessage string }{ { options: ConvertOptions{ Build: &randomBuildValue, }, errorMessage: fmt.Sprintf("unexpected Value for Build field. Possible values are: %v, %v, and %v", string(LOCAL), string(BUILD_CONFIG), string(NONE)), }, { options: ConvertOptions{ VolumeType: &randomVolumeTypeValue, }, errorMessage: fmt.Sprintf("unexpected Value for VolumeType field. Possible values are: %v, %v, %v, %v", string(PVC), string(EMPTYDIR), string(HOSTPATH), string(CONFIGMAP)), }, { options: ConvertOptions{ Provider: Kubernetes{ Controller: &randomKubernetesControllerValue, }, }, errorMessage: fmt.Sprintf("unexpected Value for Kubernetes Controller field. Possible values are: %v, %v, and %v", string(DEPLOYMENT), string(DAEMONSET), string(REPLICATION_CONTROLLER)), }, { options: ConvertOptions{ Provider: Kubernetes{ ServiceGroupMode: &randomKubernetesServiceGroupModeValue, }, }, errorMessage: fmt.Sprintf("unexpected Value for Kubernetes Service Groupe Mode field. Possible values are: %v, %v, ''", string(LABEL), string(VOLUME)), }, { options: ConvertOptions{ Provider: Kubernetes{}, Build: &buildConfigValue, }, errorMessage: fmt.Sprintf("the build value %v is only supported for Openshift provider", string(BUILD_CONFIG)), }, } client, err := NewClient() assert.Check(t, is.Equal(err, nil)) for _, tc := range testCases { _, err := client.Convert(tc.options) assert.Check(t, is.Equal(err.Error(), tc.errorMessage)) } } func TestConvertWithDefaultOptions(t *testing.T) { client, err := NewClient(WithErrorOnWarning()) assert.Check(t, is.Equal(err, nil)) objects, err := client.Convert(ConvertOptions{ ToStdout: true, InputFiles: []string{ "./testdata/docker-compose.yaml", }, }) assert.Check(t, is.Equal(err, nil)) for _, object := range objects { if deployment, ok := object.(*appsv1.Deployment); ok { assert.Check(t, is.Equal(int(*deployment.Spec.Replicas), 1)) } } } func TestConvertWithProfiles(t *testing.T) { client, err := NewClient(WithErrorOnWarning()) assert.Check(t, is.Equal(err, nil)) type Want struct { deploymentsNames []string servicesNames []string } tests := []struct { name string options ConvertOptions want Want }{ { name: "No profiles provided", options: ConvertOptions{ ToStdout: true, InputFiles: []string{ "./testdata/docker-compose-profiles.yaml", }, }, want: Want{ deploymentsNames: nil, servicesNames: nil, }, }, { name: "All profiles provided", options: ConvertOptions{ ToStdout: true, InputFiles: []string{ "./testdata/docker-compose-profiles.yaml", }, Profiles: []string{"hello", "world"}, }, want: Want{ deploymentsNames: []string{"backend", "frontend", "database"}, servicesNames: []string{"backend", "frontend", "database"}, }, }, { name: "One profile only", options: ConvertOptions{ ToStdout: true, InputFiles: []string{ "./testdata/docker-compose-profiles.yaml", }, Profiles: []string{"hello"}, }, want: Want{ deploymentsNames: []string{"backend", "frontend"}, servicesNames: []string{"backend", "frontend"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { objects, err := client.Convert(tt.options) assert.Check(t, is.Equal(err, nil)) sort.Strings(tt.want.deploymentsNames) sort.Strings(tt.want.servicesNames) var deploymentsNames []string var servicesNames []string for _, object := range objects { if deployment, ok := object.(*appsv1.Deployment); ok { deploymentsNames = append(deploymentsNames, deployment.Name) } if service, ok := object.(*v1.Service); ok { servicesNames = append(servicesNames, service.Name) } } sort.Strings(deploymentsNames) sort.Strings(servicesNames) assert.Check(t, is.DeepEqual(deploymentsNames, tt.want.deploymentsNames)) assert.Check(t, is.DeepEqual(servicesNames, tt.want.servicesNames)) }) } } ================================================ FILE: client/options.go ================================================ package client // Opt is a configuration option to initialize a client type Opt func(*Kompose) error func WithSuppressWarnings() Opt { return func(k *Kompose) error { k.suppressWarnings = true return nil } } func WithVerboseOutput() Opt { return func(k *Kompose) error { k.verbose = true return nil } } func WithErrorOnWarning() Opt { return func(k *Kompose) error { k.errorOnWarning = true return nil } } ================================================ FILE: client/options_test.go ================================================ package client import ( "testing" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestNewClientWithOpts(t *testing.T) { testCases := []struct { expectedError error expectedSuppressWarnings bool expectedVerbose bool expectedErrorOnWarnings bool opts []Opt }{ { expectedError: nil, expectedSuppressWarnings: false, expectedVerbose: false, expectedErrorOnWarnings: false, opts: []Opt{}, }, { expectedError: nil, expectedSuppressWarnings: true, expectedVerbose: false, expectedErrorOnWarnings: false, opts: []Opt{WithSuppressWarnings()}, }, { expectedError: nil, expectedSuppressWarnings: false, expectedVerbose: true, expectedErrorOnWarnings: false, opts: []Opt{WithVerboseOutput()}, }, { expectedError: nil, expectedSuppressWarnings: false, expectedVerbose: false, expectedErrorOnWarnings: true, opts: []Opt{WithErrorOnWarning()}, }, { expectedError: nil, expectedSuppressWarnings: true, expectedVerbose: false, expectedErrorOnWarnings: true, opts: []Opt{WithErrorOnWarning(), WithSuppressWarnings()}, }, } for _, tc := range testCases { client, err := NewClient(tc.opts...) assert.Check(t, is.Equal(err, tc.expectedError)) assert.Check(t, is.Equal(client.errorOnWarning, tc.expectedErrorOnWarnings)) assert.Check(t, is.Equal(client.verbose, tc.expectedVerbose)) assert.Check(t, is.Equal(client.suppressWarnings, tc.expectedSuppressWarnings)) } } ================================================ FILE: client/testdata/docker-compose-profiles.yaml ================================================ version: '3' services: backend: image: dummy:tag profiles: ['hello', 'world'] ports: - "80:80" frontend: image: dummy:tag profiles: [ 'hello' ] ports: - "80:80" database: image: dummy:tag profiles: [ 'world' ] ports: - "80:80" ================================================ FILE: client/testdata/docker-compose.yaml ================================================ version: '3' services: web: image: nginx:latest ports: - "80:80" ================================================ FILE: client/types.go ================================================ package client type ConvertBuild string const ( LOCAL ConvertBuild = "local" BUILD_CONFIG ConvertBuild = "build-config" NONE ConvertBuild = "none" ) type KubernetesController string const ( DEPLOYMENT KubernetesController = "deployment" DAEMONSET KubernetesController = "daemonSet" REPLICATION_CONTROLLER KubernetesController = "replicationController" ) type ServiceGroupMode string const ( LABEL ServiceGroupMode = "label" VOLUME ServiceGroupMode = "volume" ) type VolumeType string const ( PVC = "persistentVolumeClaim" EMPTYDIR = "emptyDir" HOSTPATH = "hostPath" CONFIGMAP = "configMap" ) type ConvertOptions struct { Build *string PushImage bool PushImageRegistry string GenerateJson bool ToStdout bool OutFile string Replicas *int VolumeType *string PvcRequestSize string WithKomposeAnnotations *bool InputFiles []string Profiles []string Provider GenerateNetworkPolicies bool } type Provider interface{} type Kubernetes struct { Provider Chart bool Controller *string MultiContainerMode bool ServiceGroupMode *string ServiceGroupName string SecretsAsFiles bool } type Openshift struct { Provider DeploymentConfig bool InsecureRepository bool BuildRepo string BuildBranch string } ================================================ FILE: cmd/completion.go ================================================ package cmd import ( "bytes" "fmt" "io" "os" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var completion = &cobra.Command{ Use: "completion SHELL", Short: "Output shell completion code", Long: `Generates shell completion code. Auto completion supports bash, zsh and fish. Output is to STDOUT. source <(kompose completion bash) source <(kompose completion zsh) kompose completion fish | source Will load the shell completion code. `, RunE: func(cmd *cobra.Command, args []string) error { err := Generate(cmd, args) if err != nil { log.Fatalf("Error: %s", err) } return nil }, } // Generate the appropriate autocompletion file func Generate(cmd *cobra.Command, args []string) error { // Check the passed in arguments if len(args) == 0 { return fmt.Errorf("shell not specified. ex. kompose completion [bash|zsh|fish]") } if len(args) > 1 { return fmt.Errorf("too many arguments. Expected only the shell type. ex. kompose completion [bash|zsh|fish]") } // Generate bash through cobra if selected switch args[0] { case "bash": return cmd.Root().GenBashCompletion(os.Stdout) case "zsh": return runCompletionZsh(os.Stdout, cmd.Root()) case "fish": return runCompletionFish(os.Stdout, cmd.Root()) default: return fmt.Errorf("not a compatible shell, bash, zsh and fish are only supported") } } func init() { RootCmd.AddCommand(completion) } /* Fish shell auto-completion support */ func runCompletionFish(out io.Writer, kompose *cobra.Command) error { kompose.GenFishCompletion(out, true) fishInitialization := ` set -l commands "completion convert help version" complete -c kompose -f complete -c kompose -n "not __fish_seen_subcommand_from $commands" -a $commands complete -c kompose -n "__fish_seen_subcommand_from completion" -a "bash zsh fish" ` out.Write([]byte(fishInitialization)) return nil } /* This is copied from https://github.com/kubernetes/kubernetes/blob/ea18d5c32ee7c320fe96dda6b0c757476908e696/pkg/kubectl/cmd/completion.go in order to generate ZSH completion support. */ func runCompletionZsh(out io.Writer, kompose *cobra.Command) error { zshInitialization := ` #compdef kompose __kompose_bash_source() { alias shopt=':' alias _expand=_bash_expand alias _complete=_bash_comp emulate -L sh setopt kshglob noshglob braceexpand source "$@" } __kompose_type() { # -t is not supported by zsh if [ "$1" == "-t" ]; then shift # fake Bash 4 to disable "complete -o nospace". Instead # "compopt +-o nospace" is used in the code to toggle trailing # spaces. We don't support that, but leave trailing spaces on # all the time if [ "$1" = "__kompose_compopt" ]; then echo builtin return 0 fi fi type "$@" } __kompose_compgen() { local completions w completions=( $(compgen "$@") ) || return $? # filter by given word as prefix while [[ "$1" = -* && "$1" != -- ]]; do shift shift done if [[ "$1" == -- ]]; then shift fi for w in "${completions[@]}"; do if [[ "${w}" = "$1"* ]]; then echo "${w}" fi done } __kompose_compopt() { true # don't do anything. Not supported by bashcompinit in zsh } __kompose_declare() { if [ "$1" == "-F" ]; then whence -w "$@" else builtin declare "$@" fi } __kompose_ltrim_colon_completions() { if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then # Remove colon-word prefix from COMPREPLY items local colon_word=${1%${1##*:}} local i=${#COMPREPLY[*]} while [[ $((--i)) -ge 0 ]]; do COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} done fi } __kompose_get_comp_words_by_ref() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[${COMP_CWORD}-1]}" words=("${COMP_WORDS[@]}") cword=("${COMP_CWORD[@]}") } __kompose_filedir() { local RET OLD_IFS w qw __debug "_filedir $@ cur=$cur" if [[ "$1" = \~* ]]; then # somehow does not work. Maybe, zsh does not call this at all eval echo "$1" return 0 fi OLD_IFS="$IFS" IFS=$'\n' if [ "$1" = "-d" ]; then shift RET=( $(compgen -d) ) else RET=( $(compgen -f) ) fi IFS="$OLD_IFS" IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" for w in ${RET[@]}; do if [[ ! "${w}" = "${cur}"* ]]; then continue fi if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then qw="$(__kompose_quote "${w}")" if [ -d "${w}" ]; then COMPREPLY+=("${qw}/") else COMPREPLY+=("${qw}") fi fi done } __kompose_quote() { if [[ $1 == \'* || $1 == \"* ]]; then # Leave out first character printf %q "${1:1}" else printf %q "$1" fi } autoload -U +X bashcompinit && bashcompinit # use word boundary patterns for BSD or GNU sed LWORD='[[:<:]]' RWORD='[[:>:]]' if sed --help 2>&1 | grep -q GNU; then LWORD='\<' RWORD='\>' fi __kompose_convert_bash_to_zsh() { sed \ -e 's/declare -F/whence -w/' \ -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ -e "s/${LWORD}_filedir${RWORD}/__kompose_filedir/g" \ -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kompose_get_comp_words_by_ref/g" \ -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kompose_ltrim_colon_completions/g" \ -e "s/${LWORD}compgen${RWORD}/__kompose_compgen/g" \ -e "s/${LWORD}compopt${RWORD}/__kompose_compopt/g" \ -e "s/${LWORD}declare${RWORD}/__kompose_declare/g" \ -e "s/\\\$(type${RWORD}/\$(__kompose_type/g" \ <<'BASH_COMPLETION_EOF' ` out.Write([]byte(zshInitialization)) buf := new(bytes.Buffer) kompose.GenBashCompletion(buf) out.Write(buf.Bytes()) zshTail := ` BASH_COMPLETION_EOF } __kompose_bash_source <(__kompose_convert_bash_to_zsh) ` out.Write([]byte(zshTail)) return nil } ================================================ FILE: cmd/convert.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package cmd import ( "strings" "github.com/kubernetes/kompose/pkg/app" "github.com/kubernetes/kompose/pkg/kobject" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) // TODO: comment var ( ConvertOut string ConvertBuildRepo string ConvertBuildBranch string ConvertBuild string ConvertVolumes string ConvertPVCRequestSize string ConvertChart bool ConvertDeployment bool ConvertDaemonSet bool ConvertReplicationController bool ConvertYaml bool ConvertJSON bool ConvertStdout bool ConvertEmptyVols bool ConvertInsecureRepo bool ConvertDeploymentConfig bool ConvertReplicas int ConvertController string ConvertProfiles []string ConvertPushImage bool ConvertNamespace string ConvertPushImageRegistry string ConvertOpt kobject.ConvertOptions ConvertYAMLIndent int GenerateNetworkPolicies bool UpBuild string BuildCommand string PushCommand string // WithKomposeAnnotation decides if we will add metadata about this convert to resource's annotation. // default is true. WithKomposeAnnotation bool // NoInterpolation decides if we will interpolate environment variables in the compose file. NoInterpolate bool // MultipleContainerMode which enables creating multi containers in a single pod is a developing function. // default is false MultipleContainerMode bool ServiceGroupMode string ServiceGroupName string // SecretsAsFiles forces secrets to result in files inside a container instead of symlinked directories containing // files of the same name. This reproduces the behavior of file-based secrets in docker-compose and should probably // be the default for kompose, but we must keep compatibility with the previous behavior. // See https://github.com/kubernetes/kompose/issues/1280 for more details. SecretsAsFiles bool ) var convertCmd = &cobra.Command{ Use: "convert", Short: "Convert a Compose file", Example: ` kompose --file compose.yaml convert kompose -f first.yaml -f second.yaml convert kompose --provider openshift --file compose.yaml convert`, PreRun: func(cmd *cobra.Command, args []string) { // Check that build-config wasn't passed in with --provider=kubernetes if GlobalProvider == "kubernetes" && UpBuild == "build-config" { log.Fatalf("build-config is not a valid --build parameter with provider Kubernetes") } // Create the Convert Options. ConvertOpt = kobject.ConvertOptions{ ToStdout: ConvertStdout, CreateChart: ConvertChart, GenerateYaml: ConvertYaml, GenerateJSON: ConvertJSON, Replicas: ConvertReplicas, InputFiles: GlobalFiles, OutFile: ConvertOut, Provider: GlobalProvider, CreateD: ConvertDeployment, CreateDS: ConvertDaemonSet, CreateRC: ConvertReplicationController, Build: ConvertBuild, BuildRepo: ConvertBuildRepo, BuildBranch: ConvertBuildBranch, PushImage: ConvertPushImage, PushImageRegistry: ConvertPushImageRegistry, CreateDeploymentConfig: ConvertDeploymentConfig, EmptyVols: ConvertEmptyVols, Volumes: ConvertVolumes, PVCRequestSize: ConvertPVCRequestSize, InsecureRepository: ConvertInsecureRepo, IsDeploymentFlag: cmd.Flags().Lookup("deployment").Changed, IsDaemonSetFlag: cmd.Flags().Lookup("daemon-set").Changed, IsReplicationControllerFlag: cmd.Flags().Lookup("replication-controller").Changed, Controller: strings.ToLower(ConvertController), IsReplicaSetFlag: cmd.Flags().Lookup("replicas").Changed, IsDeploymentConfigFlag: cmd.Flags().Lookup("deployment-config").Changed, YAMLIndent: ConvertYAMLIndent, Profiles: ConvertProfiles, WithKomposeAnnotation: WithKomposeAnnotation, NoInterpolate: NoInterpolate, MultipleContainerMode: MultipleContainerMode, ServiceGroupMode: ServiceGroupMode, ServiceGroupName: ServiceGroupName, SecretsAsFiles: SecretsAsFiles, GenerateNetworkPolicies: GenerateNetworkPolicies, BuildCommand: BuildCommand, PushCommand: PushCommand, Namespace: ConvertNamespace, } if ServiceGroupMode == "" && MultipleContainerMode { ConvertOpt.ServiceGroupMode = "label" } app.ValidateFlags(args, cmd, &ConvertOpt) // Since ValidateComposeFiles returns an error, let's validate it and output the error appropriately if the validation fails err := app.ValidateComposeFile(&ConvertOpt) if err != nil { log.Fatalf("Error validating compose file: %v", err) } }, Run: func(cmd *cobra.Command, args []string) { app.Convert(ConvertOpt) }, } func init() { // Automatically grab environment variables viper.AutomaticEnv() // Kubernetes only convertCmd.Flags().BoolVarP(&ConvertChart, "chart", "c", false, "Create a Helm chart for converted objects") convertCmd.Flags().BoolVar(&ConvertDaemonSet, "daemon-set", false, "Generate a Kubernetes daemonset object (deprecated, use --controller instead)") convertCmd.Flags().BoolVarP(&ConvertDeployment, "deployment", "d", false, "Generate a Kubernetes deployment object (deprecated, use --controller instead)") convertCmd.Flags().BoolVar(&ConvertReplicationController, "replication-controller", false, "Generate a Kubernetes replication controller object (deprecated, use --controller instead)") convertCmd.Flags().StringVar(&ConvertController, "controller", "", `Set the output controller ("deployment"|"daemonSet"|"replicationController")`) convertCmd.Flags().MarkDeprecated("daemon-set", "use --controller") convertCmd.Flags().MarkDeprecated("deployment", "use --controller") convertCmd.Flags().MarkDeprecated("replication-controller", "use --controller") convertCmd.Flags().MarkHidden("chart") convertCmd.Flags().MarkHidden("daemon-set") convertCmd.Flags().MarkHidden("replication-controller") convertCmd.Flags().MarkHidden("deployment") convertCmd.Flags().BoolVar(&MultipleContainerMode, "multiple-container-mode", false, "Create multiple containers grouped by 'kompose.service.group' label") convertCmd.Flags().StringVar(&ServiceGroupMode, "service-group-mode", "", "Group multiple service to create single workload by `label`(`kompose.service.group`) or `volume`(shared volumes)") convertCmd.Flags().StringVar(&ServiceGroupName, "service-group-name", "", "Using with --service-group-mode=volume to specific a final service name for the group") convertCmd.Flags().MarkDeprecated("multiple-container-mode", "use --service-group-mode=label") convertCmd.Flags().BoolVar(&SecretsAsFiles, "secrets-as-files", false, "Always convert docker-compose secrets into files instead of symlinked directories") // OpenShift only convertCmd.Flags().BoolVar(&ConvertDeploymentConfig, "deployment-config", true, "Generate an OpenShift deploymentconfig object") convertCmd.Flags().BoolVar(&ConvertInsecureRepo, "insecure-repository", false, "Use an insecure Docker repository for OpenShift ImageStream") convertCmd.Flags().StringVar(&ConvertBuildRepo, "build-repo", "", "Specify source repository for buildconfig (default remote origin)") convertCmd.Flags().StringVar(&ConvertBuildBranch, "build-branch", "", "Specify repository branch to use for buildconfig (default master)") convertCmd.Flags().MarkDeprecated("deployment-config", "use --controller") convertCmd.Flags().MarkHidden("deployment-config") convertCmd.Flags().MarkHidden("insecure-repository") convertCmd.Flags().MarkHidden("build-repo") convertCmd.Flags().MarkHidden("build-branch") // Standard between the two convertCmd.Flags().StringVar(&ConvertBuild, "build", "none", `Set the type of build ("local"|"build-config"(OpenShift only)|"none")`) convertCmd.Flags().BoolVar(&ConvertPushImage, "push-image", false, "If we should push the docker image we built") convertCmd.Flags().StringVar(&BuildCommand, "build-command", "", `Set the command used to build the container image, which will override the docker build command. Should be used in conjuction with --push-command flag.`) convertCmd.Flags().StringVar(&PushCommand, "push-command", "", `Set the command used to push the container image. override the docker push command. Should be used in conjuction with --build-command flag.`) convertCmd.Flags().StringVar(&ConvertPushImageRegistry, "push-image-registry", "", "Specify registry for pushing image, which will override registry from image name") convertCmd.Flags().BoolVarP(&ConvertYaml, "yaml", "y", false, "Generate resource files into YAML format") convertCmd.Flags().MarkDeprecated("yaml", "YAML is the default format now") convertCmd.Flags().MarkShorthandDeprecated("y", "YAML is the default format now") convertCmd.Flags().BoolVarP(&ConvertJSON, "json", "j", false, "Generate resource files into JSON format") convertCmd.Flags().BoolVar(&ConvertStdout, "stdout", false, "Print converted objects to stdout") convertCmd.Flags().StringVarP(&ConvertOut, "out", "o", "", "Specify a file name or directory to save objects to (if path does not exist, a file will be created)") convertCmd.Flags().IntVar(&ConvertReplicas, "replicas", 1, "Specify the number of replicas in the generated resource spec") convertCmd.Flags().StringVar(&ConvertVolumes, "volumes", "persistentVolumeClaim", `Volumes to be generated ("persistentVolumeClaim"|"emptyDir"|"hostPath" | "configMap")`) convertCmd.Flags().StringVar(&ConvertPVCRequestSize, "pvc-request-size", "", `Specify the size of pvc storage requests in the generated resource spec`) convertCmd.Flags().StringVarP(&ConvertNamespace, "namespace", "n", "", `Specify the namespace of the generated resources`) convertCmd.Flags().BoolVar(&GenerateNetworkPolicies, "generate-network-policies", false, "Specify whether to generate network policies or not") convertCmd.Flags().BoolVar(&WithKomposeAnnotation, "with-kompose-annotation", true, "Add kompose annotations to generated resource") convertCmd.Flags().BoolVar(&NoInterpolate, "no-interpolate", false, "Keep environment variable names in the Compose file") // Deprecated commands convertCmd.Flags().BoolVar(&ConvertEmptyVols, "emptyvols", false, "Use Empty Volumes. Do not generate PVCs") convertCmd.Flags().MarkDeprecated("emptyvols", "emptyvols has been marked as deprecated. Use --volumes emptyDir") convertCmd.Flags().IntVar(&ConvertYAMLIndent, "indent", 2, "Spaces length to indent generated yaml files") convertCmd.Flags().StringArrayVar(&ConvertProfiles, "profile", []string{}, `Specify the profile to use, can use multiple profiles`) // In order to 'separate' both OpenShift and Kubernetes only flags. A custom help page is created customHelp := `Usage:{{if .Runnable}} {{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}} {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} Aliases: {{.NameAndAliases}} {{end}}{{if .HasExample}} Examples: {{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableLocalFlags}} Kubernetes Flags: -c, --chart Create a Helm chart for converted objects --controller Set the output controller ("deployment"|"daemonSet"|"replicationController") --service-group-mode Group multiple service to create single workload by "label"("kompose.service.group") or "volume"(shared volumes) --service-group-name Using with --service-group-mode=volume to specific a final service name for the group OpenShift Flags: --build-branch Specify repository branch to use for buildconfig (default is current branch name) --build-repo Specify source repository for buildconfig (default is current branch's remote url) --insecure-repository Specify to use insecure docker repository while generating Openshift image stream object Flags: {{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableSubCommands }} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} ` // Set the help template + add the command to root convertCmd.SetUsageTemplate(customHelp) RootCmd.AddCommand(convertCmd) } ================================================ FILE: cmd/root.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package cmd import ( "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" ) // Logrus hooks // Hook for error and exit out on warning type errorOnWarningHook struct{} func (errorOnWarningHook) Levels() []log.Level { return []log.Level{log.WarnLevel} } func (errorOnWarningHook) Fire(entry *log.Entry) error { log.Fatalln(entry.Message) return nil } // TODO: comment var ( GlobalProvider string GlobalVerbose bool GlobalSuppressWarnings bool GlobalErrorOnWarning bool GlobalFiles []string ) // RootCmd root level flags and commands var RootCmd = &cobra.Command{ Use: "kompose", Short: "A tool helping Compose users move to Kubernetes", Long: `Kompose is a tool to help users who are familiar with docker-compose move to Kubernetes.`, Example: ` kompose --file compose.yaml convert kompose -f first.yaml -f second.yaml convert kompose --provider openshift --file compose.yaml convert kompose completion bash`, SilenceErrors: true, // PersistentPreRun will be "inherited" by all children and ran before *every* command unless // the child has overridden the functionality. This functionality was implemented to check / modify // all global flag calls regardless of app call. PersistentPreRun: func(cmd *cobra.Command, args []string) { // Add extra logging when verbosity is passed if GlobalVerbose { log.SetLevel(log.DebugLevel) } // Disable the timestamp (Kompose is too fast!) formatter := new(log.TextFormatter) formatter.DisableTimestamp = true formatter.ForceColors = true log.SetFormatter(formatter) // Set the appropriate suppress warnings and error on warning flags if GlobalSuppressWarnings { log.SetLevel(log.ErrorLevel) } else if GlobalErrorOnWarning { hook := errorOnWarningHook{} log.AddHook(hook) } // Error out of the user has not chosen Kubernetes or OpenShift provider := strings.ToLower(GlobalProvider) if provider != "kubernetes" && provider != "openshift" { log.Fatalf("%s is an unsupported provider. Supported providers are: 'kubernetes', 'openshift'.", GlobalProvider) } v := viper.New() v.BindEnv("file", "COMPOSE_FILE") cmd.Flags().VisitAll(func(f *pflag.Flag) { configName := f.Name if configName == "file" && !f.Changed && v.IsSet(configName) { GlobalFiles = v.GetStringSlice(configName) } }) }, } // Execute executes the root level command. // It returns an error if any. func Execute() error { return RootCmd.Execute() } func init() { RootCmd.PersistentFlags().BoolVarP(&GlobalVerbose, "verbose", "v", false, "verbose output") RootCmd.PersistentFlags().BoolVar(&GlobalSuppressWarnings, "suppress-warnings", false, "Suppress all warnings") RootCmd.PersistentFlags().BoolVar(&GlobalErrorOnWarning, "error-on-warning", false, "Treat any warning as an error") RootCmd.PersistentFlags().StringSliceVarP(&GlobalFiles, "file", "f", []string{}, "Specify an alternative compose file") RootCmd.PersistentFlags().StringVar(&GlobalProvider, "provider", "kubernetes", "Specify a provider. Kubernetes or OpenShift.") } ================================================ FILE: cmd/version.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package cmd import ( "fmt" "github.com/kubernetes/kompose/pkg/version" "github.com/spf13/cobra" ) // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version of Kompose", Run: func(cmd *cobra.Command, args []string) { // See pkg/version/version.go for more information as to why we use the git commit / hash value fmt.Println(version.VERSION + " (" + version.GITCOMMIT + ")") }, } func init() { RootCmd.AddCommand(versionCmd) } ================================================ FILE: code-of-conduct.md ================================================ # Kubernetes Community Code of Conduct Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) ================================================ FILE: docs/404.html ================================================ --- permalink: /404.html layout: default --- <style type="text/css" media="screen"> .container { margin: 10px auto; max-width: 600px; text-align: center; } h1 { margin: 30px 0; font-size: 4em; line-height: 1; letter-spacing: -1px; } </style> <div class="container"> <h1>404</h1> <p><strong>Page not found :(</strong></p> <p>The requested page could not be found.</p> </div> ================================================ FILE: docs/CNAME ================================================ kompose.io ================================================ FILE: docs/Gemfile ================================================ source "https://rubygems.org" # Hello! This is where you manage which Jekyll version is used to run. # When you want to use a different version, change it below, save the # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: # # bundle exec jekyll serve # # This will help ensure the proper Jekyll version is running. # Happy Jekylling! gem "jekyll", "~> 4.0.0" # This is the default theme for new Jekyll sites. You may change this to anything you like. gem "minima", "~> 2.5" # If you want to use GitHub Pages, remove the "gem "jekyll"" above and # uncomment the line below. To upgrade, run `bundle update github-pages`. # gem "github-pages", group: :jekyll_plugins # If you have any plugins, put them here! group :jekyll_plugins do gem "jekyll-feed", "~> 0.12" end # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem # and associated library. install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do gem "tzinfo", "~> 1.2" gem "tzinfo-data" end # Performance-booster for watching directories on Windows gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform? gem "jekyll-redirect-from" ================================================ FILE: docs/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014-2016 GochoMugo <mugo@forfuture.co.ke> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: docs/README.md ================================================ View the site via: ```sh bundle exec jekyll serve . ``` And visiting `localhost:4000` on your browser. ================================================ FILE: docs/_config.yml ================================================ title: Kompose name: Kompose email: foo@gmail.com github_page: https://github.com/kubernetes/kompose slack_page: https://slack.k8s.io description: >- # this means to ignore newlines until "baseurl:" Convert your Docker Compose file to Kubernetes or OpenShift baseurl: "" # the subpath of your site, e.g. /blog url: "" # the base hostname & protocol for your site, e.g. http://example.com year: 2024 # Google Analytics number (starts with UA?) analytics: 12345 instagram: https://www.instagram.com facebook: https://www.facebook.com # Build settings theme: minima plugins: - jekyll-feed ================================================ FILE: docs/_data/dates.yml ================================================ - date: "Saturday May 8" green: 20 yellow: 20 red: 20 - date: "Monday May 17" green: 20 yellow: 20 red: 20 - date: "Monday May 31" green: 20 yellow: 20 red: 20 - date: "Monday June 7" green: 20 yellow: 20 red: 20 - date: "Monday June 14" green: 20 yellow: 20 red: 20 - date: "Monday June 21" green: 20 yellow: 20 red: 20 - date: "Monday July 5" green: 20 yellow: 20 red: 20 - date: "Monday July 19" green: 20 yellow: 20 red: 20 - date: "Monday July 26" green: 20 yellow: 20 red: 20 - date: "Monday August 9" green: 20 yellow: 20 red: 20 - date: "Monday August 16" green: 20 yellow: 20 red: 20 - date: "Monday August 23" green: 20 yellow: 20 red: 20 - date: "Monday August 30" green: 20 yellow: 20 red: 20 - date: "Monday September 13" green: 20 yellow: 20 red: 20 - date: "Friday September 24" green: 20 yellow: 20 red: 20 - date: "Monday September 27" green: 20 yellow: 20 red: 20 - date: "Monday October 4" green: 20 yellow: 20 red: 20 - date: "Sunday October 10" green: 20 yellow: 20 red: 20 ================================================ FILE: docs/_data/menu.yml ================================================ other_links: "Installation": "/installation/" "Getting Started": "/getting-started/" "User Guide": "/user-guide/" "Conversion Matrix": "/conversion/" "Architecture": "/architecture/" ================================================ FILE: docs/_foobar.yml ================================================ dates: "May 8th 2021" ================================================ FILE: docs/_includes/footer.html ================================================ <!-- Footer --> <div class="footer"> <div class="container text-center"> <span class="copyright"> We are a Kubernetes incubator graduated project and officially part of the Kubernetes group <br> Apache License 2.0 licensed project <br> © {{ site.year }} Kompose Authors -- All Rights Reserved <br /> <a class="trademarks" href="https://www.linuxfoundation.org/legal/trademark-usage" target="_blank" ref="noopener" >Trademarks</a > </span> </div> <div class="container"> <div class="row text-center"> <div class="col-lg-2 col-md-3 col-sm-12"> <div class="footer-logo"> <h2>{{ site.name }}</h2> </div> </div> <div class="col-lg-6 col-md-6 col-sm-12"> <ul class="footer-menu"> <!-- {% for item in site.data.menu.footer %} <li><a href="{{ item[1] }}">{{ item[0] }}</a></li> {% endfor %} --> </ul> </div> <div class="col-lg-4 col-md-3 col-sm-12"> <div class="footer-links"> <ul> <li><a href="{{ site.slack_page }}" target="_blank"><img class="img-fluid" src="/assets/icons/slack.png" alt="Slack">Slack</a> </li> <li><a href="{{ site.github_page }}" target="_blank"><img class="img-fluid" src="/assets/icons/github.png" alt="GitHub">GitHub</a> </li> </ul> </div> </div> </div> <!-- Scroll To Top --> <!--<a id="back-top" class="back-to-top js-scroll-trigger" href="#main"></a>--> <!-- Scroll To Top Ends--> </div> </div> ================================================ FILE: docs/_includes/meta.html ================================================ <!-- Meta --> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="description" content="{{site.description}}"> <!-- Cross-site Meta --> <meta name="robots" content="index, follow"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="language" content="English"> <!-- Metadata loading stuff--> <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" media="all" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:200,300,400,400i,500,600,700%7CMontserrat:300,400,500%7CRoboto" rel="stylesheet"> <link rel="stylesheet" href="/assets/css/animate.css"> <!-- Resource style --> <link rel="stylesheet" href="/assets/css/owl.carousel.css"> <link rel="stylesheet" href="/assets/css/owl.theme.css"> <link rel="stylesheet" href="/assets/css/magnific-popup.css"> <link rel="stylesheet" href="/assets/css/ionicons.min.css"> <!-- Resource style --> <link rel="stylesheet" href="/assets/css/style.css"> <link rel="stylesheet" href="/assets/css/github-markdown.css"> <!-- Favicons --> <!-- Generate using: https://realfavicongenerator.net/ and put information in /assets/favicons/ folder --> <link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicons/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicons/favicon-16x16.png"> <link rel="manifest" href="/assets/favicons/site.webmanifest"> <link rel="mask-icon" href="/assets/favicons/safari-pinned-tab.svg" color="#5bbad5"> <link rel="shortcut icon" href="/assets/favicons/favicon.ico"> <meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-config" content="/assets/favicons/browserconfig.xml"> <meta name="theme-color" content="#ffffff"> <!-- ANALYTICS --> <!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id={{ site.analytics }}"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '{{ site.analytics }}'); </script> ================================================ FILE: docs/_includes/navbar.html ================================================ <!-- Navbar Section --> <nav class="navbar navbar-expand-md navbar-light bg-light fixed-top"> <div class="container"> <a class="navbar-brand" href="/"> <img class="navbar-logo" src="/assets/images/logo.png" alt="logo"/> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav ml-auto navbar-right"> {% for item in site.data.menu.other_links %} <li class="nav-item"><a class="nav-link" href="{{ item[1] }}">{{ item[0] }}</a></li> {% endfor %} </ul> </div> </div> </nav><!-- Navbar End --> ================================================ FILE: docs/_layouts/default.html ================================================ <!DOCTYPE html> <whtml lang="en"> <head> <!-- Metadata --> <meta charset="utf-8"> <title>{{ site.title }} - {{ page.title }} {% include meta.html %}
{% include navbar.html %}
{{ content }}

{% include footer.html %}
================================================ FILE: docs/_layouts/index.html ================================================ {{ site.title }} - {{ site.description }} {% include meta.html %}
{% include navbar.html %}

An official Kubernetes project, located at github.com/kubernetes/kompose

Go from Docker Compose to Kubernetes

Kompose

Kompose is a conversion tool for Docker Compose to container orchestrators such as Kubernetes (or OpenShift).

Installation
{{ content }}

Get started on Kubernetes immediately

So easy your human companion could do it too!

Why do cats (and developers) like Kompose?

Developers love to simplify their development environment with Docker Compose.

With Kompose, you can now push the same file to a production container orchestrator!

Getting Started

Built for container engineers

Our conversions are not always 1-1 from Docker Compose to Kubernetes, but we will help get you 99% of the way there!

The awesome features

• Compatibility with multiple versions of Docker Compose
• A conversion matrix that outlines all compatible values and versions
• An in-depth user guide to use advanced features such as LoadBalancer, Service and TLS
Labels that provide the extra 1% needed to get to 1-1 conversion

User Guide
{% include footer.html %}
================================================ FILE: docs/architecture.md ================================================ --- layout: default permalink: /architecture/ title: Architecture redirect_from: - /docs/architecture.md/ - /docs/architecture/ --- # Architecture and Internal Design * TOC {:toc} `kompose` has 3 stages: _Loader_, _Transformer_ and _Outputter_. Each stage should have a well-defined interface, so it is easy to write a new Loader, Transformer, or Outputters and plug it in. Currently, only Loader and Transformer interfaces are defined. ![Design Diagram](https://raw.githubusercontent.com/kubernetes/kompose/main/docs/images/design_diagram.png) ## Loader The Loader reads the input file now `kompose` supports [Compose](https://docs.docker.com/compose) v1, v2 and converts it to KomposeObject. Loader is represented by a Loader interface: ```go type Loader interface { LoadFile(file string) kobject.KomposeObject } ``` Every loader “implementation” should be placed into `kompose/pkg/loader` (like compose). More input formats will be supported in the future. You can take a look for more details at: - [kompose/pkg/loader](https://github.com/kubernetes/kompose/tree/master/pkg/loader) - [kompose/pkg/loader/compose](https://github.com/kubernetes/kompose/tree/master/pkg/loader/compose) ## KomposeObject `KomposeObject` is Kompose internal representation of all containers loaded from input file. First version of `KomposeObject` looks like this (source: [kobject.go](https://github.com/kubernetes/kompose/blob/master/pkg/kobject/kobject.go)): ```go // KomposeObject holds the generic struct of Kompose transformation type KomposeObject struct { ServiceConfigs map[string]ServiceConfig } // ServiceConfig holds the basic struct of a container type ServiceConfig struct { ContainerName string Image string Environment []EnvVar Port []Ports Command []string WorkingDir string Args []string Volumes []string Network []string Labels map[string]string Annotations map[string]string CPUSet string CPUShares int64 CPUQuota int64 CapAdd []string CapDrop []string Entrypoint []string Expose []string Privileged bool Restart string User string } ``` ## Transformer The Transformer takes KomposeObject and converts it to target/output format (currently, there are sets of Kubernetes/OpenShift objects). Similar to the `Loader`, Transformer is represented by a Transformer interface: ```go type Transformer interface { Transform(kobject.KomposeObject, kobject.ConvertOptions) []runtime.Object } ``` If you wish to add more providers containing different kinds of objects, the Transformer would be the place to look into. Currently, Kompose supports Kubernetes (by default) and OpenShift providers. More details at: - [kompose/pkg/transformer](https://github.com/kubernetes/kompose/tree/master/pkg/transformer) - [kompose/pkg/transformer/Kubernetes](https://github.com/kubernetes/kompose/tree/master/pkg/transformer/kubernetes) - [kompose/pkg/transformer/openshift](https://github.com/kubernetes/kompose/tree/master/pkg/transformer/openshift) ## Outputter The Outputter takes the Transformer result and executes the given action. For example, action can display results to stdout or directly deploy artifacts to Kubernetes/OpenShift. ================================================ FILE: docs/assets/css/animate.css ================================================ /*! Animate.css - http://daneden.me/animate Licensed under the MIT license Copyright (c) 2013 Daniel Eden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .animated.hinge { -webkit-animation-duration: 2s; animation-duration: 2s; } @-webkit-keyframes bounce { 0%, 20%, 50%, 80%, 100% { -webkit-transform: translateY(0); transform: translateY(0); } 40% { -webkit-transform: translateY(-30px); transform: translateY(-30px); } 60% { -webkit-transform: translateY(-15px); transform: translateY(-15px); } } @keyframes bounce { 0%, 20%, 50%, 80%, 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 40% { -webkit-transform: translateY(-30px); -ms-transform: translateY(-30px); transform: translateY(-30px); } 60% { -webkit-transform: translateY(-15px); -ms-transform: translateY(-15px); transform: translateY(-15px); } } .bounce { -webkit-animation-name: bounce; animation-name: bounce; } @-webkit-keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } @keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } .flash { -webkit-animation-name: flash; animation-name: flash; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes pulse { 0% { -webkit-transform: scale(1); transform: scale(1); } 50% { -webkit-transform: scale(1.1); transform: scale(1.1); } 100% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes pulse { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 50% { -webkit-transform: scale(1.1); -ms-transform: scale(1.1); transform: scale(1.1); } 100% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } } .pulse { -webkit-animation-name: pulse; animation-name: pulse; } @-webkit-keyframes shake { 0%, 100% { -webkit-transform: translateX(0); transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translateX(-10px); transform: translateX(-10px); } 20%, 40%, 60%, 80% { -webkit-transform: translateX(10px); transform: translateX(10px); } } @keyframes shake { 0%, 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translateX(-10px); -ms-transform: translateX(-10px); transform: translateX(-10px); } 20%, 40%, 60%, 80% { -webkit-transform: translateX(10px); -ms-transform: translateX(10px); transform: translateX(10px); } } .shake { -webkit-animation-name: shake; animation-name: shake; } @-webkit-keyframes swing { 20% { -webkit-transform: rotate(15deg); transform: rotate(15deg); } 40% { -webkit-transform: rotate(-10deg); transform: rotate(-10deg); } 60% { -webkit-transform: rotate(5deg); transform: rotate(5deg); } 80% { -webkit-transform: rotate(-5deg); transform: rotate(-5deg); } 100% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } } @keyframes swing { 20% { -webkit-transform: rotate(15deg); -ms-transform: rotate(15deg); transform: rotate(15deg); } 40% { -webkit-transform: rotate(-10deg); -ms-transform: rotate(-10deg); transform: rotate(-10deg); } 60% { -webkit-transform: rotate(5deg); -ms-transform: rotate(5deg); transform: rotate(5deg); } 80% { -webkit-transform: rotate(-5deg); -ms-transform: rotate(-5deg); transform: rotate(-5deg); } 100% { -webkit-transform: rotate(0deg); -ms-transform: rotate(0deg); transform: rotate(0deg); } } .swing { -webkit-transform-origin: top center; -ms-transform-origin: top center; transform-origin: top center; -webkit-animation-name: swing; animation-name: swing; } @-webkit-keyframes tada { 0% { -webkit-transform: scale(1); transform: scale(1); } 10%, 20% { -webkit-transform: scale(0.9) rotate(-3deg); transform: scale(0.9) rotate(-3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale(1.1) rotate(3deg); transform: scale(1.1) rotate(3deg); } 40%, 60%, 80% { -webkit-transform: scale(1.1) rotate(-3deg); transform: scale(1.1) rotate(-3deg); } 100% { -webkit-transform: scale(1) rotate(0); transform: scale(1) rotate(0); } } @keyframes tada { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 10%, 20% { -webkit-transform: scale(0.9) rotate(-3deg); -ms-transform: scale(0.9) rotate(-3deg); transform: scale(0.9) rotate(-3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale(1.1) rotate(3deg); -ms-transform: scale(1.1) rotate(3deg); transform: scale(1.1) rotate(3deg); } 40%, 60%, 80% { -webkit-transform: scale(1.1) rotate(-3deg); -ms-transform: scale(1.1) rotate(-3deg); transform: scale(1.1) rotate(-3deg); } 100% { -webkit-transform: scale(1) rotate(0); -ms-transform: scale(1) rotate(0); transform: scale(1) rotate(0); } } .tada { -webkit-animation-name: tada; animation-name: tada; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes wobble { 0% { -webkit-transform: translateX(0%); transform: translateX(0%); } 15% { -webkit-transform: translateX(-25%) rotate(-5deg); transform: translateX(-25%) rotate(-5deg); } 30% { -webkit-transform: translateX(20%) rotate(3deg); transform: translateX(20%) rotate(3deg); } 45% { -webkit-transform: translateX(-15%) rotate(-3deg); transform: translateX(-15%) rotate(-3deg); } 60% { -webkit-transform: translateX(10%) rotate(2deg); transform: translateX(10%) rotate(2deg); } 75% { -webkit-transform: translateX(-5%) rotate(-1deg); transform: translateX(-5%) rotate(-1deg); } 100% { -webkit-transform: translateX(0%); transform: translateX(0%); } } @keyframes wobble { 0% { -webkit-transform: translateX(0%); -ms-transform: translateX(0%); transform: translateX(0%); } 15% { -webkit-transform: translateX(-25%) rotate(-5deg); -ms-transform: translateX(-25%) rotate(-5deg); transform: translateX(-25%) rotate(-5deg); } 30% { -webkit-transform: translateX(20%) rotate(3deg); -ms-transform: translateX(20%) rotate(3deg); transform: translateX(20%) rotate(3deg); } 45% { -webkit-transform: translateX(-15%) rotate(-3deg); -ms-transform: translateX(-15%) rotate(-3deg); transform: translateX(-15%) rotate(-3deg); } 60% { -webkit-transform: translateX(10%) rotate(2deg); -ms-transform: translateX(10%) rotate(2deg); transform: translateX(10%) rotate(2deg); } 75% { -webkit-transform: translateX(-5%) rotate(-1deg); -ms-transform: translateX(-5%) rotate(-1deg); transform: translateX(-5%) rotate(-1deg); } 100% { -webkit-transform: translateX(0%); -ms-transform: translateX(0%); transform: translateX(0%); } } .wobble { -webkit-animation-name: wobble; animation-name: wobble; } @-webkit-keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(.3); transform: scale(.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); transform: scale(1.05); } 70% { -webkit-transform: scale(.9); transform: scale(.9); } 100% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(.3); -ms-transform: scale(.3); transform: scale(.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); -ms-transform: scale(1.05); transform: scale(1.05); } 70% { -webkit-transform: scale(.9); -ms-transform: scale(.9); transform: scale(.9); } 100% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } } .bounceIn { -webkit-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); -ms-transform: translateY(30px); transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); -ms-transform: translateY(-10px); transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .bounceInDown { -webkit-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes bounceInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 60% { opacity: 1; -webkit-transform: translateX(30px); transform: translateX(30px); } 80% { -webkit-transform: translateX(-10px); transform: translateX(-10px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes bounceInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 60% { opacity: 1; -webkit-transform: translateX(30px); -ms-transform: translateX(30px); transform: translateX(30px); } 80% { -webkit-transform: translateX(-10px); -ms-transform: translateX(-10px); transform: translateX(-10px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .bounceInLeft { -webkit-animation-name: bounceInLeft; animation-name: bounceInLeft; } @-webkit-keyframes bounceInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 60% { opacity: 1; -webkit-transform: translateX(-30px); transform: translateX(-30px); } 80% { -webkit-transform: translateX(10px); transform: translateX(10px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes bounceInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 60% { opacity: 1; -webkit-transform: translateX(-30px); -ms-transform: translateX(-30px); transform: translateX(-30px); } 80% { -webkit-transform: translateX(10px); -ms-transform: translateX(10px); transform: translateX(10px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .bounceInRight { -webkit-animation-name: bounceInRight; animation-name: bounceInRight; } @-webkit-keyframes bounceInUp { 0% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } 60% { opacity: 1; -webkit-transform: translateY(-30px); transform: translateY(-30px); } 80% { -webkit-transform: translateY(10px); transform: translateY(10px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes bounceInUp { 0% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } 60% { opacity: 1; -webkit-transform: translateY(-30px); -ms-transform: translateY(-30px); transform: translateY(-30px); } 80% { -webkit-transform: translateY(10px); -ms-transform: translateY(10px); transform: translateY(10px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .bounceInUp { -webkit-animation-name: bounceInUp; animation-name: bounceInUp; } @-webkit-keyframes bounceOut { 0% { -webkit-transform: scale(1); transform: scale(1); } 25% { -webkit-transform: scale(.95); transform: scale(.95); } 50% { opacity: 1; -webkit-transform: scale(1.1); transform: scale(1.1); } 100% { opacity: 0; -webkit-transform: scale(.3); transform: scale(.3); } } @keyframes bounceOut { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 25% { -webkit-transform: scale(.95); -ms-transform: scale(.95); transform: scale(.95); } 50% { opacity: 1; -webkit-transform: scale(1.1); -ms-transform: scale(1.1); transform: scale(1.1); } 100% { opacity: 0; -webkit-transform: scale(.3); -ms-transform: scale(.3); transform: scale(.3); } } .bounceOut { -webkit-animation-name: bounceOut; animation-name: bounceOut; } @-webkit-keyframes bounceOutDown { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } } @keyframes bounceOutDown { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } } .bounceOutDown { -webkit-animation-name: bounceOutDown; animation-name: bounceOutDown; } @-webkit-keyframes bounceOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes bounceOutLeft { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .bounceOutLeft { -webkit-animation-name: bounceOutLeft; animation-name: bounceOutLeft; } @-webkit-keyframes bounceOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes bounceOutRight { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .bounceOutRight { -webkit-animation-name: bounceOutRight; animation-name: bounceOutRight; } @-webkit-keyframes bounceOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes bounceOutUp { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .bounceOutUp { -webkit-animation-name: bounceOutUp; animation-name: bounceOutUp; } @-webkit-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } .fadeIn { -webkit-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInLeft { -webkit-animation-name: fadeInLeft; animation-name: fadeInLeft; } @-webkit-keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInLeftBig { -webkit-animation-name: fadeInLeftBig; animation-name: fadeInLeftBig; } @-webkit-keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInRight { -webkit-animation-name: fadeInRight; animation-name: fadeInRight; } @-webkit-keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInRightBig { -webkit-animation-name: fadeInRightBig; animation-name: fadeInRightBig; } @-webkit-keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInUp { -webkit-animation-name: fadeInUp; animation-name: fadeInUp; } @-webkit-keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInUpBig { -webkit-animation-name: fadeInUpBig; animation-name: fadeInUpBig; } @-webkit-keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .fadeOut { -webkit-animation-name: fadeOut; animation-name: fadeOut; } @-webkit-keyframes fadeOutDown { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(20px); transform: translateY(20px); } } @keyframes fadeOutDown { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } } .fadeOutDown { -webkit-animation-name: fadeOutDown; animation-name: fadeOutDown; } @-webkit-keyframes fadeOutDownBig { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } } @keyframes fadeOutDownBig { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } } .fadeOutDownBig { -webkit-animation-name: fadeOutDownBig; animation-name: fadeOutDownBig; } @-webkit-keyframes fadeOutLeft { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-20px); transform: translateX(-20px); } } @keyframes fadeOutLeft { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } } .fadeOutLeft { -webkit-animation-name: fadeOutLeft; animation-name: fadeOutLeft; } @-webkit-keyframes fadeOutLeftBig { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes fadeOutLeftBig { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .fadeOutLeftBig { -webkit-animation-name: fadeOutLeftBig; animation-name: fadeOutLeftBig; } @-webkit-keyframes fadeOutRight { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(20px); transform: translateX(20px); } } @keyframes fadeOutRight { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } } .fadeOutRight { -webkit-animation-name: fadeOutRight; animation-name: fadeOutRight; } @-webkit-keyframes fadeOutRightBig { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes fadeOutRightBig { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .fadeOutRightBig { -webkit-animation-name: fadeOutRightBig; animation-name: fadeOutRightBig; } @-webkit-keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } } @keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } @-webkit-keyframes fadeOutUpBig { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes fadeOutUpBig { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .fadeOutUpBig { -webkit-animation-name: fadeOutUpBig; animation-name: fadeOutUpBig; } @-webkit-keyframes flip { 0% { -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } @keyframes flip { 0% { -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -ms-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -ms-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -ms-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } .animated.flip { -webkit-backface-visibility: visible; -ms-backface-visibility: visible; backface-visibility: visible; -webkit-animation-name: flip; animation-name: flip; } @-webkit-keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateX(-10deg); transform: perspective(400px) rotateX(-10deg); } 70% { -webkit-transform: perspective(400px) rotateX(10deg); transform: perspective(400px) rotateX(10deg); } 100% { -webkit-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } } @keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotateX(90deg); -ms-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateX(-10deg); -ms-transform: perspective(400px) rotateX(-10deg); transform: perspective(400px) rotateX(-10deg); } 70% { -webkit-transform: perspective(400px) rotateX(10deg); -ms-transform: perspective(400px) rotateX(10deg); transform: perspective(400px) rotateX(10deg); } 100% { -webkit-transform: perspective(400px) rotateX(0deg); -ms-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } } .flipInX { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInX; animation-name: flipInX; } @-webkit-keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateY(-10deg); transform: perspective(400px) rotateY(-10deg); } 70% { -webkit-transform: perspective(400px) rotateY(10deg); transform: perspective(400px) rotateY(10deg); } 100% { -webkit-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } } @keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotateY(90deg); -ms-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateY(-10deg); -ms-transform: perspective(400px) rotateY(-10deg); transform: perspective(400px) rotateY(-10deg); } 70% { -webkit-transform: perspective(400px) rotateY(10deg); -ms-transform: perspective(400px) rotateY(10deg); transform: perspective(400px) rotateY(10deg); } 100% { -webkit-transform: perspective(400px) rotateY(0deg); -ms-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } } .flipInY { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInY; animation-name: flipInY; } @-webkit-keyframes flipOutX { 0% { -webkit-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } } @keyframes flipOutX { 0% { -webkit-transform: perspective(400px) rotateX(0deg); -ms-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateX(90deg); -ms-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } } .flipOutX { -webkit-animation-name: flipOutX; animation-name: flipOutX; -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; } @-webkit-keyframes flipOutY { 0% { -webkit-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } } @keyframes flipOutY { 0% { -webkit-transform: perspective(400px) rotateY(0deg); -ms-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateY(90deg); -ms-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } } .flipOutY { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipOutY; animation-name: flipOutY; } @-webkit-keyframes lightSpeedIn { 0% { -webkit-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: translateX(-20%) skewX(30deg); transform: translateX(-20%) skewX(30deg); opacity: 1; } 80% { -webkit-transform: translateX(0%) skewX(-15deg); transform: translateX(0%) skewX(-15deg); opacity: 1; } 100% { -webkit-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } } @keyframes lightSpeedIn { 0% { -webkit-transform: translateX(100%) skewX(-30deg); -ms-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: translateX(-20%) skewX(30deg); -ms-transform: translateX(-20%) skewX(30deg); transform: translateX(-20%) skewX(30deg); opacity: 1; } 80% { -webkit-transform: translateX(0%) skewX(-15deg); -ms-transform: translateX(0%) skewX(-15deg); transform: translateX(0%) skewX(-15deg); opacity: 1; } 100% { -webkit-transform: translateX(0%) skewX(0deg); -ms-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } } .lightSpeedIn { -webkit-animation-name: lightSpeedIn; animation-name: lightSpeedIn; -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } @-webkit-keyframes lightSpeedOut { 0% { -webkit-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } 100% { -webkit-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } } @keyframes lightSpeedOut { 0% { -webkit-transform: translateX(0%) skewX(0deg); -ms-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } 100% { -webkit-transform: translateX(100%) skewX(-30deg); -ms-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } } .lightSpeedOut { -webkit-animation-name: lightSpeedOut; animation-name: lightSpeedOut; -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } @-webkit-keyframes rotateIn { 0% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(-200deg); transform: rotate(-200deg); opacity: 0; } 100% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateIn { 0% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(-200deg); -ms-transform: rotate(-200deg); transform: rotate(-200deg); opacity: 0; } 100% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateIn { -webkit-animation-name: rotateIn; animation-name: rotateIn; } @-webkit-keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInDownLeft { -webkit-animation-name: rotateInDownLeft; animation-name: rotateInDownLeft; } @-webkit-keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInDownRight { -webkit-animation-name: rotateInDownRight; animation-name: rotateInDownRight; } @-webkit-keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInUpLeft { -webkit-animation-name: rotateInUpLeft; animation-name: rotateInUpLeft; } @-webkit-keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInUpRight { -webkit-animation-name: rotateInUpRight; animation-name: rotateInUpRight; } @-webkit-keyframes rotateOut { 0% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(200deg); transform: rotate(200deg); opacity: 0; } } @keyframes rotateOut { 0% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(200deg); -ms-transform: rotate(200deg); transform: rotate(200deg); opacity: 0; } } .rotateOut { -webkit-animation-name: rotateOut; animation-name: rotateOut; } @-webkit-keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } @keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } .rotateOutDownLeft { -webkit-animation-name: rotateOutDownLeft; animation-name: rotateOutDownLeft; } @-webkit-keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } @keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } .rotateOutDownRight { -webkit-animation-name: rotateOutDownRight; animation-name: rotateOutDownRight; } @-webkit-keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } @keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } .rotateOutUpLeft { -webkit-animation-name: rotateOutUpLeft; animation-name: rotateOutUpLeft; } @-webkit-keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } @keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } .rotateOutUpRight { -webkit-animation-name: rotateOutUpRight; animation-name: rotateOutUpRight; } @-webkit-keyframes slideInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes slideInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .slideInDown { -webkit-animation-name: slideInDown; animation-name: slideInDown; } @-webkit-keyframes slideInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .slideInLeft { -webkit-animation-name: slideInLeft; animation-name: slideInLeft; } @-webkit-keyframes slideInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .slideInRight { -webkit-animation-name: slideInRight; animation-name: slideInRight; } @-webkit-keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .slideOutLeft { -webkit-animation-name: slideOutLeft; animation-name: slideOutLeft; } @-webkit-keyframes slideOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes slideOutRight { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .slideOutRight { -webkit-animation-name: slideOutRight; animation-name: slideOutRight; } @-webkit-keyframes slideOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes slideOutUp { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .slideOutUp { -webkit-animation-name: slideOutUp; animation-name: slideOutUp; } @-webkit-keyframes hinge { 0% { -webkit-transform: rotate(0); transform: rotate(0); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate(80deg); transform: rotate(80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40% { -webkit-transform: rotate(60deg); transform: rotate(60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 80% { -webkit-transform: rotate(60deg) translateY(0); transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 100% { -webkit-transform: translateY(700px); transform: translateY(700px); opacity: 0; } } @keyframes hinge { 0% { -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate(80deg); -ms-transform: rotate(80deg); transform: rotate(80deg); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40% { -webkit-transform: rotate(60deg); -ms-transform: rotate(60deg); transform: rotate(60deg); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 80% { -webkit-transform: rotate(60deg) translateY(0); -ms-transform: rotate(60deg) translateY(0); transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 100% { -webkit-transform: translateY(700px); -ms-transform: translateY(700px); transform: translateY(700px); opacity: 0; } } .hinge { -webkit-animation-name: hinge; animation-name: hinge; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollIn { 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); transform: translateX(-100%) rotate(-120deg); } 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } } @keyframes rollIn { 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); -ms-transform: translateX(-100%) rotate(-120deg); transform: translateX(-100%) rotate(-120deg); } 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); -ms-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } } .rollIn { -webkit-animation-name: rollIn; animation-name: rollIn; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollOut { 0% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } 100% { opacity: 0; -webkit-transform: translateX(100%) rotate(120deg); transform: translateX(100%) rotate(120deg); } } @keyframes rollOut { 0% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); -ms-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } 100% { opacity: 0; -webkit-transform: translateX(100%) rotate(120deg); -ms-transform: translateX(100%) rotate(120deg); transform: translateX(100%) rotate(120deg); } } .rollOut { -webkit-animation-name: rollOut; animation-name: rollOut; } ================================================ FILE: docs/assets/css/github-markdown.css ================================================ @media (prefers-color-scheme: dark) { .markdown-body { color-scheme: dark; --color-prettylights-syntax-comment: #8b949e; --color-prettylights-syntax-constant: #79c0ff; --color-prettylights-syntax-entity: #d2a8ff; --color-prettylights-syntax-storage-modifier-import: #c9d1d9; --color-prettylights-syntax-entity-tag: #7ee787; --color-prettylights-syntax-keyword: #ff7b72; --color-prettylights-syntax-string: #a5d6ff; --color-prettylights-syntax-variable: #ffa657; --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; --color-prettylights-syntax-invalid-illegal-bg: #8e1519; --color-prettylights-syntax-carriage-return-text: #f0f6fc; --color-prettylights-syntax-carriage-return-bg: #b62324; --color-prettylights-syntax-string-regexp: #7ee787; --color-prettylights-syntax-markup-list: #f2cc60; --color-prettylights-syntax-markup-heading: #1f6feb; --color-prettylights-syntax-markup-italic: #c9d1d9; --color-prettylights-syntax-markup-bold: #c9d1d9; --color-prettylights-syntax-markup-deleted-text: #ffdcd7; --color-prettylights-syntax-markup-deleted-bg: #67060c; --color-prettylights-syntax-markup-inserted-text: #aff5b4; --color-prettylights-syntax-markup-inserted-bg: #033a16; --color-prettylights-syntax-markup-changed-text: #ffdfb6; --color-prettylights-syntax-markup-changed-bg: #5a1e02; --color-prettylights-syntax-markup-ignored-text: #c9d1d9; --color-prettylights-syntax-markup-ignored-bg: #1158c7; --color-prettylights-syntax-meta-diff-range: #d2a8ff; --color-prettylights-syntax-brackethighlighter-angle: #8b949e; --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; --color-fg-default: #c9d1d9; --color-fg-muted: #8b949e; --color-fg-subtle: #484f58; --color-canvas-default: #0d1117; --color-canvas-subtle: #161b22; --color-border-default: #30363d; --color-border-muted: #21262d; --color-neutral-muted: rgba(110,118,129,0.4); --color-accent-fg: #58a6ff; --color-accent-emphasis: #1f6feb; --color-attention-subtle: rgba(187,128,9,0.15); --color-danger-fg: #f85149; } } @media (prefers-color-scheme: light) { .markdown-body { color-scheme: light; --color-prettylights-syntax-comment: #6e7781; --color-prettylights-syntax-constant: #0550ae; --color-prettylights-syntax-entity: #8250df; --color-prettylights-syntax-storage-modifier-import: #24292f; --color-prettylights-syntax-entity-tag: #116329; --color-prettylights-syntax-keyword: #cf222e; --color-prettylights-syntax-string: #0a3069; --color-prettylights-syntax-variable: #953800; --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; --color-prettylights-syntax-invalid-illegal-bg: #82071e; --color-prettylights-syntax-carriage-return-text: #f6f8fa; --color-prettylights-syntax-carriage-return-bg: #cf222e; --color-prettylights-syntax-string-regexp: #116329; --color-prettylights-syntax-markup-list: #3b2300; --color-prettylights-syntax-markup-heading: #0550ae; --color-prettylights-syntax-markup-italic: #24292f; --color-prettylights-syntax-markup-bold: #24292f; --color-prettylights-syntax-markup-deleted-text: #82071e; --color-prettylights-syntax-markup-deleted-bg: #FFEBE9; --color-prettylights-syntax-markup-inserted-text: #116329; --color-prettylights-syntax-markup-inserted-bg: #dafbe1; --color-prettylights-syntax-markup-changed-text: #953800; --color-prettylights-syntax-markup-changed-bg: #ffd8b5; --color-prettylights-syntax-markup-ignored-text: #eaeef2; --color-prettylights-syntax-markup-ignored-bg: #0550ae; --color-prettylights-syntax-meta-diff-range: #8250df; --color-prettylights-syntax-brackethighlighter-angle: #57606a; --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; --color-prettylights-syntax-constant-other-reference-link: #0a3069; --color-fg-default: #24292f; --color-fg-muted: #57606a; --color-fg-subtle: #6e7781; --color-canvas-default: #ffffff; --color-canvas-subtle: #f6f8fa; --color-border-default: #d0d7de; --color-border-muted: hsla(210,18%,87%,1); --color-neutral-muted: rgba(175,184,193,0.2); --color-accent-fg: #0969da; --color-accent-emphasis: #0969da; --color-attention-subtle: #fff8c5; --color-danger-fg: #cf222e; } } .markdown-body { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; margin: 0; color: var(--color-fg-default); background-color: var(--color-canvas-default); font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; font-size: 16px; line-height: 1.5; word-wrap: break-word; } .markdown-body .octicon { display: inline-block; fill: currentColor; vertical-align: text-bottom; } .markdown-body h1:hover .anchor .octicon-link:before, .markdown-body h2:hover .anchor .octicon-link:before, .markdown-body h3:hover .anchor .octicon-link:before, .markdown-body h4:hover .anchor .octicon-link:before, .markdown-body h5:hover .anchor .octicon-link:before, .markdown-body h6:hover .anchor .octicon-link:before { width: 16px; height: 16px; content: ' '; display: inline-block; background-color: currentColor; -webkit-mask-image: url("data:image/svg+xml,"); mask-image: url("data:image/svg+xml,"); } .markdown-body details, .markdown-body figcaption, .markdown-body figure { display: block; } .markdown-body summary { display: list-item; } .markdown-body [hidden] { display: none !important; } .markdown-body a { background-color: transparent; color: var(--color-accent-fg); text-decoration: none; } .markdown-body a:active, .markdown-body a:hover { outline-width: 0; } .markdown-body abbr[title] { border-bottom: none; text-decoration: underline dotted; } .markdown-body b, .markdown-body strong { font-weight: 600; } .markdown-body dfn { font-style: italic; } .markdown-body h1 { margin: .67em 0; font-weight: 600; padding-bottom: .3em; font-size: 2em; border-bottom: 1px solid var(--color-border-muted); } .markdown-body mark { background-color: var(--color-attention-subtle); color: var(--color-text-primary); } .markdown-body small { font-size: 90%; } .markdown-body sub, .markdown-body sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } .markdown-body sub { bottom: -0.25em; } .markdown-body sup { top: -0.5em; } .markdown-body img { border-style: none; max-width: 100%; box-sizing: content-box; background-color: var(--color-canvas-default); } .markdown-body code, .markdown-body kbd, .markdown-body pre, .markdown-body samp { font-family: monospace,monospace; font-size: 1em; } .markdown-body figure { margin: 1em 40px; } .markdown-body hr { box-sizing: content-box; overflow: hidden; background: transparent; border-bottom: 1px solid var(--color-border-muted); height: .25em; padding: 0; margin: 24px 0; background-color: var(--color-border-default); border: 0; } .markdown-body input { font: inherit; margin: 0; overflow: visible; font-family: inherit; font-size: inherit; line-height: inherit; } .markdown-body [type=button], .markdown-body [type=reset], .markdown-body [type=submit] { -webkit-appearance: button; } .markdown-body [type=button]::-moz-focus-inner, .markdown-body [type=reset]::-moz-focus-inner, .markdown-body [type=submit]::-moz-focus-inner { border-style: none; padding: 0; } .markdown-body [type=button]:-moz-focusring, .markdown-body [type=reset]:-moz-focusring, .markdown-body [type=submit]:-moz-focusring { outline: 1px dotted ButtonText; } .markdown-body [type=checkbox], .markdown-body [type=radio] { box-sizing: border-box; padding: 0; } .markdown-body [type=number]::-webkit-inner-spin-button, .markdown-body [type=number]::-webkit-outer-spin-button { height: auto; } .markdown-body [type=search] { -webkit-appearance: textfield; outline-offset: -2px; } .markdown-body [type=search]::-webkit-search-cancel-button, .markdown-body [type=search]::-webkit-search-decoration { -webkit-appearance: none; } .markdown-body ::-webkit-input-placeholder { color: inherit; opacity: .54; } .markdown-body ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; } .markdown-body a:hover { text-decoration: underline; } .markdown-body hr::before { display: table; content: ""; } .markdown-body hr::after { display: table; clear: both; content: ""; } .markdown-body table { border-spacing: 0; border-collapse: collapse; display: block; width: max-content; max-width: 100%; overflow: auto; } .markdown-body td, .markdown-body th { padding: 0; } .markdown-body details summary { cursor: pointer; } .markdown-body details:not([open])>*:not(summary) { display: none !important; } .markdown-body kbd { display: inline-block; padding: 3px 5px; font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; line-height: 10px; color: var(--color-fg-default); vertical-align: middle; background-color: var(--color-canvas-subtle); border: solid 1px var(--color-neutral-muted); border-bottom-color: var(--color-neutral-muted); border-radius: 6px; box-shadow: inset 0 -1px 0 var(--color-neutral-muted); } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; } .markdown-body h2 { font-weight: 600; padding-bottom: .3em; font-size: 1.5em; border-bottom: 1px solid var(--color-border-muted); } .markdown-body h3 { font-weight: 600; font-size: 1.25em; } .markdown-body h4 { font-weight: 600; font-size: 1em; } .markdown-body h5 { font-weight: 600; font-size: .875em; } .markdown-body h6 { font-weight: 600; font-size: .85em; color: var(--color-fg-muted); } .markdown-body p { margin-top: 0; margin-bottom: 10px; } .markdown-body blockquote { margin: 0; padding: 0 1em; color: var(--color-fg-muted); border-left: .25em solid var(--color-border-default); } .markdown-body ul, .markdown-body ol { margin-top: 0; margin-bottom: 0; padding-left: 2em; } .markdown-body ol ol, .markdown-body ul ol { list-style-type: lower-roman; } .markdown-body ul ul ol, .markdown-body ul ol ol, .markdown-body ol ul ol, .markdown-body ol ol ol { list-style-type: lower-alpha; } .markdown-body dd { margin-left: 0; } .markdown-body tt, .markdown-body code { font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; font-size: 12px; } .markdown-body pre { margin-top: 0; margin-bottom: 0; font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; font-size: 12px; word-wrap: normal; } .markdown-body .octicon { display: inline-block; overflow: visible !important; vertical-align: text-bottom; fill: currentColor; } .markdown-body ::placeholder { color: var(--color-fg-subtle); opacity: 1; } .markdown-body input::-webkit-outer-spin-button, .markdown-body input::-webkit-inner-spin-button { margin: 0; -webkit-appearance: none; appearance: none; } .markdown-body .pl-c { color: var(--color-prettylights-syntax-comment); } .markdown-body .pl-c1, .markdown-body .pl-s .pl-v { color: var(--color-prettylights-syntax-constant); } .markdown-body .pl-e, .markdown-body .pl-en { color: var(--color-prettylights-syntax-entity); } .markdown-body .pl-smi, .markdown-body .pl-s .pl-s1 { color: var(--color-prettylights-syntax-storage-modifier-import); } .markdown-body .pl-ent { color: var(--color-prettylights-syntax-entity-tag); } .markdown-body .pl-k { color: var(--color-prettylights-syntax-keyword); } .markdown-body .pl-s, .markdown-body .pl-pds, .markdown-body .pl-s .pl-pse .pl-s1, .markdown-body .pl-sr, .markdown-body .pl-sr .pl-cce, .markdown-body .pl-sr .pl-sre, .markdown-body .pl-sr .pl-sra { color: var(--color-prettylights-syntax-string); } .markdown-body .pl-v, .markdown-body .pl-smw { color: var(--color-prettylights-syntax-variable); } .markdown-body .pl-bu { color: var(--color-prettylights-syntax-brackethighlighter-unmatched); } .markdown-body .pl-ii { color: var(--color-prettylights-syntax-invalid-illegal-text); background-color: var(--color-prettylights-syntax-invalid-illegal-bg); } .markdown-body .pl-c2 { color: var(--color-prettylights-syntax-carriage-return-text); background-color: var(--color-prettylights-syntax-carriage-return-bg); } .markdown-body .pl-sr .pl-cce { font-weight: bold; color: var(--color-prettylights-syntax-string-regexp); } .markdown-body .pl-ml { color: var(--color-prettylights-syntax-markup-list); } .markdown-body .pl-mh, .markdown-body .pl-mh .pl-en, .markdown-body .pl-ms { font-weight: bold; color: var(--color-prettylights-syntax-markup-heading); } .markdown-body .pl-mi { font-style: italic; color: var(--color-prettylights-syntax-markup-italic); } .markdown-body .pl-mb { font-weight: bold; color: var(--color-prettylights-syntax-markup-bold); } .markdown-body .pl-md { color: var(--color-prettylights-syntax-markup-deleted-text); background-color: var(--color-prettylights-syntax-markup-deleted-bg); } .markdown-body .pl-mi1 { color: var(--color-prettylights-syntax-markup-inserted-text); background-color: var(--color-prettylights-syntax-markup-inserted-bg); } .markdown-body .pl-mc { color: var(--color-prettylights-syntax-markup-changed-text); background-color: var(--color-prettylights-syntax-markup-changed-bg); } .markdown-body .pl-mi2 { color: var(--color-prettylights-syntax-markup-ignored-text); background-color: var(--color-prettylights-syntax-markup-ignored-bg); } .markdown-body .pl-mdr { font-weight: bold; color: var(--color-prettylights-syntax-meta-diff-range); } .markdown-body .pl-ba { color: var(--color-prettylights-syntax-brackethighlighter-angle); } .markdown-body .pl-sg { color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); } .markdown-body .pl-corl { text-decoration: underline; color: var(--color-prettylights-syntax-constant-other-reference-link); } .markdown-body [data-catalyst] { display: block; } .markdown-body g-emoji { font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; font-size: 1em; font-style: normal !important; font-weight: 400; line-height: 1; vertical-align: -0.075em; } .markdown-body g-emoji img { width: 1em; height: 1em; } .markdown-body::before { display: table; content: ""; } .markdown-body::after { display: table; clear: both; content: ""; } .markdown-body>*:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !important; } .markdown-body a:not([href]) { color: inherit; text-decoration: none; } .markdown-body .absent { color: var(--color-danger-fg); } .markdown-body .anchor { float: left; padding-right: 4px; margin-left: -20px; line-height: 1; } .markdown-body .anchor:focus { outline: none; } .markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre, .markdown-body details { margin-top: 0; margin-bottom: 16px; } .markdown-body blockquote>:first-child { margin-top: 0; } .markdown-body blockquote>:last-child { margin-bottom: 0; } .markdown-body sup>a::before { content: "["; } .markdown-body sup>a::after { content: "]"; } .markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { color: var(--color-fg-default); vertical-align: middle; visibility: hidden; } .markdown-body h1:hover .anchor, .markdown-body h2:hover .anchor, .markdown-body h3:hover .anchor, .markdown-body h4:hover .anchor, .markdown-body h5:hover .anchor, .markdown-body h6:hover .anchor { text-decoration: none; } .markdown-body h1:hover .anchor .octicon-link, .markdown-body h2:hover .anchor .octicon-link, .markdown-body h3:hover .anchor .octicon-link, .markdown-body h4:hover .anchor .octicon-link, .markdown-body h5:hover .anchor .octicon-link, .markdown-body h6:hover .anchor .octicon-link { visibility: visible; } .markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { padding: 0 .2em; font-size: inherit; } .markdown-body ul.no-list, .markdown-body ol.no-list { padding: 0; list-style-type: none; } .markdown-body ol[type="1"] { list-style-type: decimal; } .markdown-body ol[type=a] { list-style-type: lower-alpha; } .markdown-body ol[type=i] { list-style-type: lower-roman; } .markdown-body div>ol:not([type]) { list-style-type: decimal; } .markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-top: 0; margin-bottom: 0; } .markdown-body li>p { margin-top: 16px; } .markdown-body li+li { margin-top: .25em; } .markdown-body dl { padding: 0; } .markdown-body dl dt { padding: 0; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: 600; } .markdown-body dl dd { padding: 0 16px; margin-bottom: 16px; } .markdown-body table th { font-weight: 600; } .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid var(--color-border-default); } .markdown-body table tr { background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted); } .markdown-body table tr:nth-child(2n) { background-color: var(--color-canvas-subtle); } .markdown-body table img { background-color: transparent; } .markdown-body img[align=right] { padding-left: 20px; } .markdown-body img[align=left] { padding-right: 20px; } .markdown-body .emoji { max-width: none; vertical-align: text-top; background-color: transparent; } .markdown-body span.frame { display: block; overflow: hidden; } .markdown-body span.frame>span { display: block; float: left; width: auto; padding: 7px; margin: 13px 0 0; overflow: hidden; border: 1px solid var(--color-border-default); } .markdown-body span.frame span img { display: block; float: left; } .markdown-body span.frame span span { display: block; padding: 5px 0 0; clear: both; color: var(--color-fg-default); } .markdown-body span.align-center { display: block; overflow: hidden; clear: both; } .markdown-body span.align-center>span { display: block; margin: 13px auto 0; overflow: hidden; text-align: center; } .markdown-body span.align-center span img { margin: 0 auto; text-align: center; } .markdown-body span.align-right { display: block; overflow: hidden; clear: both; } .markdown-body span.align-right>span { display: block; margin: 13px 0 0; overflow: hidden; text-align: right; } .markdown-body span.align-right span img { margin: 0; text-align: right; } .markdown-body span.float-left { display: block; float: left; margin-right: 13px; overflow: hidden; } .markdown-body span.float-left span { margin: 13px 0 0; } .markdown-body span.float-right { display: block; float: right; margin-left: 13px; overflow: hidden; } .markdown-body span.float-right>span { display: block; margin: 13px auto 0; overflow: hidden; text-align: right; } .markdown-body code, .markdown-body tt { padding: .2em .4em; margin: 0; font-size: 85%; background-color: var(--color-neutral-muted); border-radius: 6px; } .markdown-body code br, .markdown-body tt br { display: none; } .markdown-body del code { text-decoration: inherit; } .markdown-body pre code { font-size: 100%; } .markdown-body pre>code { padding: 0; margin: 0; word-break: normal; white-space: pre; background: transparent; border: 0; } .markdown-body .highlight { margin-bottom: 16px; } .markdown-body .highlight pre { margin-bottom: 0; word-break: normal; } .markdown-body .highlight pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: var(--color-canvas-subtle); border-radius: 6px; } .markdown-body pre code, .markdown-body pre tt { display: inline; max-width: auto; padding: 0; margin: 0; overflow: visible; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0; } .markdown-body .csv-data td, .markdown-body .csv-data th { padding: 5px; overflow: hidden; font-size: 12px; line-height: 1; text-align: left; white-space: nowrap; } .markdown-body .csv-data .blob-num { padding: 10px 8px 9px; text-align: right; background: var(--color-canvas-default); border: 0; } .markdown-body .csv-data tr { border-top: 0; } .markdown-body .csv-data th { font-weight: 600; background: var(--color-canvas-subtle); border-top: 0; } .markdown-body .footnotes { font-size: 12px; color: var(--color-fg-muted); border-top: 1px solid var(--color-border-default); } .markdown-body .footnotes ol { padding-left: 16px; } .markdown-body .footnotes li { position: relative; } .markdown-body .footnotes li:target::before { position: absolute; top: -8px; right: -8px; bottom: -8px; left: -24px; pointer-events: none; content: ""; border: 2px solid var(--color-accent-emphasis); border-radius: 6px; } .markdown-body .footnotes li:target { color: var(--color-fg-default); } .markdown-body .footnotes .data-footnote-backref g-emoji { font-family: monospace; } .markdown-body .task-list-item { list-style-type: none; } .markdown-body .task-list-item label { font-weight: 400; } .markdown-body .task-list-item.enabled label { cursor: pointer; } .markdown-body .task-list-item+.task-list-item { margin-top: 3px; } .markdown-body .task-list-item .handle { display: none; } .markdown-body .task-list-item-checkbox { margin: 0 .2em .25em -1.6em; vertical-align: middle; } .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { margin: 0 -1.6em .25em .2em; } .markdown-body ::-webkit-calendar-picker-indicator { filter: invert(50%); } ================================================ FILE: docs/assets/css/jquery.accordion.css ================================================ /*! * jQuery Accordion 0.0.1 * (c) 2014 Victor Fernandez * MIT Licensed. */ /* Requirements */ [data-accordion] [data-content] { overflow: hidden; max-height: 0; } /* Basic Theme */ [data-accordion-group] { padding: 20px; box-shadow: 0 0px 15px rgba(0,0,0,0.06); } [data-control] { cursor: pointer; } [data-accordion] { line-height: 1; } [data-control], [data-content] > * { border-bottom: 1px solid #EEE; padding: 10px; } [data-content] [data-accordion] { border: 0; padding: 0; } [data-accordion] [data-control] { position: relative; padding-right: 40px; } [data-accordion] > [data-control]:after { content: ""; position: absolute; right: 10px; top: 12px; font-size: 25px; font-weight: 200; color: #444; height: 15px; width: 24px; background: url('../images/down.png') center center no-repeat; background-size: 50%; } [data-accordion].open > [data-control]:after { -webkit-transform: rotate(-180deg); -ms-transform: rotate(-180deg); transform: rotate(-180deg); } ================================================ FILE: docs/assets/css/magnific-popup.css ================================================ /* Magnific Popup CSS */ .mfp-bg { top: 0; left: 0; width: 100%; height: 100%; z-index: 1042; overflow: hidden; position: fixed; background: #0b0b0b; opacity: 0.8; } .mfp-wrap { top: 0; left: 0; width: 100%; height: 100%; z-index: 1043; position: fixed; outline: none !important; -webkit-backface-visibility: hidden; } .mfp-container { text-align: center; position: absolute; width: 100%; height: 100%; left: 0; top: 0; padding: 0 8px; box-sizing: border-box; } .mfp-container:before { content: ''; display: inline-block; height: 100%; vertical-align: middle; } .mfp-align-top .mfp-container:before { display: none; } .mfp-content { position: relative; display: inline-block; vertical-align: middle; margin: 0 auto; text-align: left; z-index: 1045; } .mfp-inline-holder .mfp-content, .mfp-ajax-holder .mfp-content { width: 100%; cursor: auto; } .mfp-ajax-cur { cursor: progress; } .mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close { cursor: -moz-zoom-out; cursor: -webkit-zoom-out; cursor: zoom-out; } .mfp-zoom { cursor: pointer; cursor: -webkit-zoom-in; cursor: -moz-zoom-in; cursor: zoom-in; } .mfp-auto-cursor .mfp-content { cursor: auto; } .mfp-close, .mfp-arrow, .mfp-preloader, .mfp-counter { -webkit-user-select: none; -moz-user-select: none; user-select: none; } .mfp-loading.mfp-figure { display: none; } .mfp-hide { display: none !important; } .mfp-preloader { color: #CCC; position: absolute; top: 50%; width: auto; text-align: center; margin-top: -0.8em; left: 8px; right: 8px; z-index: 1044; } .mfp-preloader a { color: #CCC; } .mfp-preloader a:hover { color: #FFF; } .mfp-s-ready .mfp-preloader { display: none; } .mfp-s-error .mfp-content { display: none; } button.mfp-close, button.mfp-arrow { overflow: visible; cursor: pointer; background: transparent; border: 0; -webkit-appearance: none; display: block; outline: none; padding: 0; z-index: 1046; box-shadow: none; touch-action: manipulation; } button::-moz-focus-inner { padding: 0; border: 0; } .mfp-close { width: 44px; height: 44px; line-height: 44px; position: absolute; right: 0; top: 0; text-decoration: none; text-align: center; opacity: 0.65; padding: 0 0 18px 10px; color: #FFF; font-style: normal; font-size: 28px; font-family: Arial, Baskerville, monospace; } .mfp-close:hover, .mfp-close:focus { opacity: 1; } .mfp-close:active { top: 1px; } .mfp-close-btn-in .mfp-close { color: #333; } .mfp-image-holder .mfp-close, .mfp-iframe-holder .mfp-close { color: #FFF; right: -6px; text-align: right; padding-right: 6px; width: 100%; } .mfp-counter { position: absolute; top: 0; right: 0; color: #CCC; font-size: 12px; line-height: 18px; white-space: nowrap; } .mfp-arrow { position: absolute; opacity: 0.65; margin: 0; top: 50%; margin-top: -55px; padding: 0; width: 90px; height: 110px; -webkit-tap-highlight-color: transparent; } .mfp-arrow:active { margin-top: -54px; } .mfp-arrow:hover, .mfp-arrow:focus { opacity: 1; } .mfp-arrow:before, .mfp-arrow:after { content: ''; display: block; width: 0; height: 0; position: absolute; left: 0; top: 0; margin-top: 35px; margin-left: 35px; border: medium inset transparent; } .mfp-arrow:after { border-top-width: 13px; border-bottom-width: 13px; top: 8px; } .mfp-arrow:before { border-top-width: 21px; border-bottom-width: 21px; opacity: 0.7; } .mfp-arrow-left { left: 0; } .mfp-arrow-left:after { border-right: 17px solid #FFF; margin-left: 31px; } .mfp-arrow-left:before { margin-left: 25px; border-right: 27px solid #3F3F3F; } .mfp-arrow-right { right: 0; } .mfp-arrow-right:after { border-left: 17px solid #FFF; margin-left: 39px; } .mfp-arrow-right:before { border-left: 27px solid #3F3F3F; } .mfp-iframe-holder { padding-top: 40px; padding-bottom: 40px; } .mfp-iframe-holder .mfp-content { line-height: 0; width: 100%; max-width: 900px; } .mfp-iframe-holder .mfp-close { top: -40px; } .mfp-iframe-scaler { width: 100%; height: 0; overflow: hidden; padding-top: 56.25%; } .mfp-iframe-scaler iframe { position: absolute; display: block; top: 0; left: 0; width: 100%; height: 100%; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); background: #000; } /* Main image in popup */ img.mfp-img { width: auto; max-width: 100%; height: auto; display: block; line-height: 0; box-sizing: border-box; padding: 40px 0 40px; margin: 0 auto; } /* The shadow behind the image */ .mfp-figure { line-height: 0; } .mfp-figure:after { content: ''; position: absolute; left: 0; top: 40px; bottom: 40px; display: block; right: 0; width: auto; height: auto; z-index: -1; box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); background: #444; } .mfp-figure small { color: #BDBDBD; display: block; font-size: 12px; line-height: 14px; } .mfp-figure figure { margin: 0; } .mfp-bottom-bar { margin-top: -36px; position: absolute; top: 100%; left: 0; width: 100%; cursor: auto; } .mfp-title { text-align: left; line-height: 18px; color: #F3F3F3; word-wrap: break-word; padding-right: 36px; } .mfp-image-holder .mfp-content { max-width: 100%; } .mfp-gallery .mfp-image-holder .mfp-figure { cursor: pointer; } @media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) { /** * Remove all paddings around the image on small screen */ .mfp-img-mobile .mfp-image-holder { padding-left: 0; padding-right: 0; } .mfp-img-mobile img.mfp-img { padding: 0; } .mfp-img-mobile .mfp-figure:after { top: 0; bottom: 0; } .mfp-img-mobile .mfp-figure small { display: inline; margin-left: 5px; } .mfp-img-mobile .mfp-bottom-bar { background: rgba(0, 0, 0, 0.6); bottom: 0; margin: 0; top: auto; padding: 3px 5px; position: fixed; box-sizing: border-box; } .mfp-img-mobile .mfp-bottom-bar:empty { padding: 0; } .mfp-img-mobile .mfp-counter { right: 5px; top: 3px; } .mfp-img-mobile .mfp-close { top: 0; right: 0; width: 35px; height: 35px; line-height: 35px; background: rgba(0, 0, 0, 0.6); position: fixed; text-align: center; padding: 0; } } @media all and (max-width: 900px) { .mfp-arrow { -webkit-transform: scale(0.75); transform: scale(0.75); } .mfp-arrow-left { -webkit-transform-origin: 0; transform-origin: 0; } .mfp-arrow-right { -webkit-transform-origin: 100%; transform-origin: 100%; } .mfp-container { padding-left: 6px; padding-right: 6px; } } ================================================ FILE: docs/assets/css/owl.carousel.css ================================================ /* * Core Owl Carousel CSS File * v1.3.3 */ /* clearfix */ .owl-carousel .owl-wrapper:after { content: "."; display: block; clear: both; visibility: hidden; line-height: 0; height: 0; } /* display none until init */ .owl-carousel{ display: none; position: relative; width: 100%; -ms-touch-action: pan-y; } .owl-carousel .owl-wrapper{ display: none; position: relative; -webkit-transform: translate3d(0px, 0px, 0px); } .owl-carousel .owl-wrapper-outer{ overflow: hidden; position: relative; width: 100%; } .owl-carousel .owl-wrapper-outer.autoHeight{ -webkit-transition: height 500ms ease-in-out; -moz-transition: height 500ms ease-in-out; -ms-transition: height 500ms ease-in-out; -o-transition: height 500ms ease-in-out; transition: height 500ms ease-in-out; } .owl-carousel .owl-item{ float: left; } .owl-controls .owl-page, .owl-controls .owl-buttons div{ cursor: pointer; } .owl-controls { -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } /* mouse grab icon */ .grabbing { cursor:url(grabbing.png) 8 8, move; } /* fix */ .owl-carousel .owl-wrapper, .owl-carousel .owl-item{ -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -ms-backface-visibility: hidden; -webkit-transform: translate3d(0,0,0); -moz-transform: translate3d(0,0,0); -ms-transform: translate3d(0,0,0); } ================================================ FILE: docs/assets/css/owl.theme.css ================================================ /* * Owl Carousel Owl Demo Theme * v1.3.3 */ .owl-theme .owl-controls{ margin-top: 10px; text-align: center; } /* Styling Next and Prev buttons */ .owl-theme .owl-controls .owl-buttons div{ color: #FFF; display: inline-block; zoom: 1; *display: inline;/*IE7 life-saver */ margin: 5px; padding: 3px 10px; font-size: 12px; -webkit-border-radius: 30px; -moz-border-radius: 30px; border-radius: 30px; background: #869791; filter: Alpha(Opacity=50);/*IE7 fix*/ opacity: 0.5; } /* Clickable class fix problem with hover on touch devices */ /* Use it for non-touch hover action */ .owl-theme .owl-controls.clickable .owl-buttons div:hover{ filter: Alpha(Opacity=100);/*IE7 fix*/ opacity: 1; text-decoration: none; } /* Styling Pagination*/ .owl-theme .owl-controls .owl-page{ display: inline-block; zoom: 1; *display: inline;/*IE7 life-saver */ } .owl-theme .owl-controls .owl-page span{ display: block; width: 12px; height: 12px; margin: 5px 7px; filter: Alpha(Opacity=50);/*IE7 fix*/ opacity: 0.5; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; background: #869791; } .owl-theme .owl-controls .owl-page.active span, .owl-theme .owl-controls.clickable .owl-page:hover span{ filter: Alpha(Opacity=100);/*IE7 fix*/ opacity: 1; } /* If PaginationNumbers is true */ .owl-theme .owl-controls .owl-page span.owl-numbers{ height: auto; width: auto; color: #FFF; padding: 2px 10px; font-size: 12px; -webkit-border-radius: 30px; -moz-border-radius: 30px; border-radius: 30px; } /* preloading images */ .owl-item.loading{ min-height: 150px; background: url(AjaxLoader.gif) no-repeat center center } ================================================ FILE: docs/assets/css/style.css ================================================ /*----- 1. Reset.css -----*/ /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } /* --- Common Styles ---*/ body { font-family: 'Open Sans'; } h1, h2, h3, h4, h5, h6 { font-family: 'Open Sans'; font-size: 16px; } p { font-family: 'Open Sans'; font-size: 14px; } /*----- Helper Classes -----*/ html * { text-rendering: optimizeLegibility !important; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } *, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } ::-moz-selection { color: #FFFFFF; background: #23D3D3; } ::selection { color: #FFFFFF; background: #23D3D3; } .nopadding { padding: 0; } .custom-padding { padding-left: 10px; padding-right: 10px; } .container-m { max-width: 1024px; margin: 0 auto !important; padding-left: 40px; padding-right: 40px; position: relative; } .container-s { max-width: 920px; margin: 0 auto !important; padding-left: 40px; padding-right: 40px; position: relative; } #loading { width: 100%; height: 100%; top: 0px; left: 0px; position: fixed; opacity: 1; background-color: #F2F2F2; z-index: 9999; text-align: center; } #loading-image { display: inline; top: 40%; position: relative; z-index: 9999; } .logo { position: fixed; top: 5%; left: 2%; z-index: 11; } .logo .tld { background: #047aed; } .tld { width: 40px; height: 40px; background: #047aed; color: #FFFFFF; border-radius: 50%; display: table; } .tld-text { display: table-cell; vertical-align: middle; text-align: center; } .tld-text a { font-family: 'Montserrat'; font-size: 14px; font-weight: 600; color: #FFFFFF !important; text-decoration: none !important; } .tld-text a::after { display: none; } /*------ Navbar Styling ------*/ .navbar { font-family: "Montserrat"; padding: 15px 0; /*height: 80px;*/ background-color: #FFFFFF !important; border-bottom: 1px solid #EFEFF1; -webkit-transition: 0.5s all ease; transition: 0.5s all ease; } .wt-border { border-bottom: 2px solid #FFFFFF !important; } .no-border { border: none !important; } .navbar .navbar-brand { font-family: 'Montserrat'; font-size: 18px; font-weight: 600; letter-spacing: 0px; color: #333347 !important; vertical-align: middle; } .navbar .navbar-brand img { vertical-align: middle; margin-right: 0.3em; } .navbar .navbar-toggler { border: none; } .navbar span.navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0,0,0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); } .navbar span.navbar-toggler-icon::before { outline: none; } .navbar .navbar-toggler:focus { outline: none; } .navbar-nav { height: auto; background: #FFFFFF; padding: 0 1.5rem; display: flex; align-items: center; -webkit-transition: 0.5s all ease; /* For Safari 3.1 to 6.0 */ transition: 0.5s all ease; } .navbar-nav .nav-item { margin-right: 0; margin-top: 1.5rem; font-size: 0.85rem; font-weight: 400; text-transform: capitalize; color: #333347; display: flex; } .navbar-nav .nav-item .nav-link { color: #333347; font-weight: 500; display: flex; vertical-align: middle; } .navbar-nav .nav-item .nav-link:hover { color: #333347; -webkit-transition: 0.5s; transition: 0.5s; } .navbar-nav .nav-item .nav-link:focus { color: #333347; } .btn-nav { display: inline-block; height: 38px; padding: 0 30px; text-align: center; font-size: 12px; font-weight: 600; line-height: 36px; letter-spacing: 1px; margin: 25px 0; text-transform: uppercase; text-decoration: none !important; white-space: nowrap; cursor: pointer; background-color: transparent; background: #047aed; color: #FFFFFF; border: 1px solid #047aed !important; border-radius: 50px; box-sizing: border-box; -webkit-transition: 0.2s; -moz-transition: 0.2s; transition: 0.2s; } .btn-nav:hover { color: #FFFFFF; background: #047aed; } .btn-nav:focus, .btn-nav:active { background: #047aed; color: #FFFFFF; } /*---- Navbar Alt -----*/ .navbar-alt { background: transparent !important; } .navbar-alt .navbar-nav { background: transparent !important; } /* .navbar-alt .btn-nav { background: #563d7c !important; border-color: #563d7c !important; }*/ .navbar-alt .nav-item .nav-link { color: #FFFFFF; } .navbar-alt .nav-item .nav-link:hover, .navbar-alt .nav-item .nav-link:focus { color: rgba(255, 255, 255, 0.7); } .navbar-alt .navbar-brand { color: #FFFFFF !important; } @media only screen and (max-width: 767px) { .navbar { padding: 15px; } .navbar .navbar-nav { background: #FFFFFF !important; padding-bottom: 25px; } .navbar-nav .nav-item { display: block; } .nav-white .nav-item .nav-link { color: #4957B8; } .navbar-alt .nav-item .nav-link { color: #333347; } .navbar .btn-cta { padding: 0 30px; } } @media only screen and (min-width: 240px) { .navbar.past-main { background: #FFFFFF !important; border-bottom: 1px solid #EFEFF1; } .navbar.effect-main { -webkit-transition: all 0.3s; transition: all 0.3s; } .navbar.past-main .navbar-brand { color: #333347 !important; } .nav-white.past-main .nav-item .nav-link { color: #333347; font-weight:500; } .navbar.past-main .nav-item .nav-link { color: #333347; font-weight: 500; } .navbar.past-main .nav-item:hover .nav-link { color: #23D3D3; } .navbar.past-main .navbar-brand { color: #333347; } /* .navbar.past-main span.navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.6)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); }*/ .nav-white.effect-main { -webkit-transition: all 0.3s; transition: all 0.3s; } } /*----------------- Hero Section Styling ------------------*/ .main { width: 100%; height: 100%; } .payment{ padding: 30px 0 75px 0; } .what { padding: 75px 0 75px 0; } .first { padding: 110px 0 75px 0 !important; background: #F7F7F8; } .home { padding: 75px 0 75px 0; background: #F7F7F8; } .home .hero-content { padding: 25px 0 0 0; } .centered { float: none !important; margin-left: auto !important; margin-right: auto !important; } .home h1 { font-size: 42px; font-weight: 300; color: #FFFFFF; color: #252525; line-height: 1.2; letter-spacing: -0.01em; } .home .hero-content-inner p { font-size: 18px; font-weight: 400; color: rgb(129, 129, 152); max-width: 630px; margin: 0 auto; letter-spacing: 0; line-height: 1.6; margin-top: 10px; margin-bottom: 15px; } .btn-success { font-family: 'Montserrat'; display: inline-block; height: 42px; padding: 0 15px; text-align: center; font-size: 12px; font-weight: 600; line-height: 40px; letter-spacing: 1px; margin: 25px 0; text-transform: uppercase; text-decoration: none !important; white-space: nowrap; cursor: pointer; background-color: transparent; background: #28a745; color: #FFFFFF; border: 1px solid #28a745 !important; border-radius: 20px; box-sizing: border-box; -webkit-transition: 0.2s; -moz-transition: 0.2s; transition: 0.2s; } .btn-success:hover { color: #FFFFFF; background: #28a745; border: 1px solid #28a745 !important; outline: none !important; text-decoration: none; } .btn-success:focus, .btn-success:active { background: #047aed; color: #FFFFFF; border: 1px solid transparent !important; outline: none !important; } .btn-action { font-family: 'Montserrat'; display: inline-block; height: 42px; padding: 0 15px; text-align: center; font-size: 12px; font-weight: 600; line-height: 40px; letter-spacing: 1px; margin: 25px 0; text-transform: uppercase; text-decoration: none !important; white-space: nowrap; cursor: pointer; background-color: transparent; background: #047aed; color: #FFFFFF; border: 1px solid #047aed !important; border-radius: 20px; box-sizing: border-box; -webkit-transition: 0.2s; -moz-transition: 0.2s; transition: 0.2s; } .btn-action:hover { color: #FFFFFF; background: #047aed; border: 1px solid #047aed !important; outline: none !important; text-decoration: none; } .btn-action:focus, .btn-action:active { background: #047aed; color: #FFFFFF; border: 1px solid transparent !important; outline: none !important; } .btn-alt { background: #047aed; color: #FFFFFF; } .btn-alt:hover { background: #047aed; color: #FFFFFF; border: 1px solid #6d48e5 !important; outline: none !important; text-decoration: none; } .btn-white { background: #FFFFFF; color: #047aed; } .btn-white:hover { background: #FFFFFF; color: #047aed; border: 1px solid #FFFFFF !important; outline: none !important; text-decoration: none; } .home .client-list { margin-top: 25px; } .home .client-list ul { list-style-type: none; text-align: center; } .home .client-list ul li { display: inline; margin: 0 20px; } .home .client-list ul li img { width: 100px; opacity: 0.6; cursor: pointer; } .home .client-list ul li img:hover { opacity: 0.9; } /*------ Home 2 Styling ------*/ .home-2 { background: #FFEEFF; background: #fafafa; background: linear-gradient(to bottom, #0ab3e4, #102976); background: #047aed; position: relative; padding: 100px 0 50px 0; } .home-2::before, .home-2::after { content: ' '; position: absolute; height: 120px; bottom: -60px; left: 0; right: 0; background: #FFFFFF; transform: skewY(-5deg); -webkit-transform: skewY(-5deg); -moz-transform: skewY(-3deg); -ms-transform: skewY(-3deg); -o-transform: skewY(-3deg); } .home-2 .hero-img img { width: 100%; margin-top: 20px; } .home-2 .hero-content { display: flex; height: 100%; padding: 0 !important; } .home-2 .hero-content .hero-content-inner { margin: auto; text-align: center; } .home-2 h1 { font-size: 42px; font-weight: 300; color: #FFFFFF; line-height: 1.2; letter-spacing: -0.01em; } .home-2 p { font-size: 18px; font-weight: 400; color: rgba(255, 255, 255, 0.7); max-width: none; letter-spacing: 0; line-height: 1.6; margin-top: 25px; margin-bottom: 0; } .home-2 .client-list ul { text-align: left; } .home-2 .client-list ul li { margin: 0 20px 0 0; } .home-2 .hero-form { position: relative; z-index: 1; } .home-2 .hero-form #contactForm { background: #FFFFFF; padding: 35px; border-radius: 10px; max-width: 380px !important; margin: 0 auto; float: none; z-index: 1111 !important; box-shadow: 0 5px 50px rgba(0, 0, 10, 0.2); } .home-2 .hero-form #contactForm label { font-family: 'Montserrat'; font-size: 14px; font-weight: 600; color: #1e266d; line-height: 1.4; margin: 10px 0; } .home-2 .hero-form #contactForm input, .home-2 .hero-form #contactForm textarea { font-family: 'Montserrat'; font-size: 14px; font-weight: 400; color: #1e266d; border: 0; border-radius: 0; border-bottom: 2px solid #b4becb; } .home-2 .hero-form #contactForm .btn-action { margin: 20px 0; min-width: 100%; } .help-block { font-family: 'Montserrat'; font-size: 12px; font-weight: 400; margin-top: 5px; color: #FF3333; font-style: normal !important; } .text-success, .text-danger { font-size: 14px; font-family: 'Poppins'; } /*------------- Home 3 Styling --------------*/ .home-3 { /*padding: 125px 0 75px 0;*/ } .home-3 .hero-content { display: flex; height: 100%; padding: 0 !important; } .home-3 .hero-content .hero-content-inner { margin: auto; text-align: center; } .home-3 .hero-content h4 { text-align:left; font-size: 14px; font-weight: 500; color: rgba(0, 0, 0, 0.4); line-height: 1.2; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; padding-top:15px; } .home-3 .hero-content h1 { text-align:left; font-size: 42px; font-weight: 300; color: rgba(0, 0, 0, 0.7); line-height: 1.2; } .home-3 .hero-content p { text-align:left; font-size: 18px; font-weight: 400; color: rgba(0, 0, 0, 0.5); max-width: none; letter-spacing: 0; line-height: 1.6; margin-top: 15px; margin-bottom: 0; } .home-3 .hero-img img { max-width: 100%; margin-top: 20px; margin-bottom: 50px; } /*------------- Form Styling -----------*/ .sub-form { position: relative; max-width: 420px; margin: 0 auto; margin-top: 30px; } .chimp-form .mail { position: relative; background-color: #FFFFFF; box-shadow: none; -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; transition: all .3s; } .sub-form input { display: block; width: 100%; margin: 0 auto; color: #6d48e5; font-family: 'Montserrat'; padding: 0; font-size: 13px; font-weight: 500; height: 50px; border: 1px solid #047aed; border-radius: 4px; background-color: #FFFFFF; outline: none; padding: 0 100px 0 20px; box-shadow: 0 5px 30px rgba(255, 255, 255, 0.1); } .sub-form input:focus { outline: none !important; background: #FFFFFF; border: 1px solid #047aed; } .sub-form .submit-button { height: 50px; width: 100%; border: 0; border-radius: 4px; margin: 0 auto; margin: 15px 0; padding: 0 25px 0 25px; background: #047aed; font-family: 'Montserrat'; font-size: 14px; font-weight: 500; text-transform: capitalize; letter-spacing: 0; color: #FFFFFF; cursor: pointer; outline: none; box-shadow: 0 2px 5px 0 rgba(0, 0, 100,.2); -webkit-transition: 300ms; -moz-transition: 300ms; transition: 300ms; } .sub-form .submit-button:hover { box-shadow: 0 5px 25px 0 rgba(0, 0, 100,.2); } .sub-form .submit-button:focus, .sub-form .submit-button:active { background: #047aed; } #email-error { position: absolute; left: 0; right: 0; bottom: -50%; font-family: 'Montserrat'; font-size: 13px; font-weight: 500; color: #FF3333; } #response { position: absolute; left: 0; bottom: -45%; vertical-align: middle; font-family: 'Montserrat'; font-size: 13px !important; font-weight: 500; color: #15ccbe; } #chimp-email-error { position: absolute; left: 0; right: 0; bottom: -45%; vertical-align: middle; font-family: 'Montserrat'; font-size: 13px; font-weight: 500; color: red; } .success-message { color:#33cc33; margin-top: 5px; } .form-note p { font-size: 12px; font-weight: 500; color: #97a6b5 } /*---------- Split Coming Soon Demo Styling -----------*/ .home-split { width: 100%; height: 100%; } .home-split .yd_flx2 { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; -webkit-flex-flow: row wrap; justify-content: space-around; line-height: 0; min-height: 100vh; } .home-split .yd_flx2 > * { flex: 1 100%; } .home-split .flx_1 { background: #1E3B70; background: #dbd9cd; background: #047aed; background: #6892d5; background: #F9F9F9; line-height: normal; order: 2; min-height: 600px; text-align: center; } .home-split .flx_2 { background: #79d1c3; background: url(../images/hg.jpg) no-repeat center center; background-size: cover; min-height: 600px; order: 1; padding: 0 !important; margin: 0 !important } .home-split .flx_1 .section-txt { display: flex; max-width: 600px; margin: auto; height: 100%; } .home-split .flx_1 .section-txt .section-txt-inner { margin: auto; /* Important */ } .home-split .flx_1 .section-txt { padding: 50px 50px; } .home-split .flx_1 h2 { font-size: 40px; font-weight: 700; color: rgba(0, 0, 0, 0.65); line-height: 1.2; letter-spacing: 0; margin-bottom: 20px; } .home-split .flx_1 h4 { font-size: 14px; font-weight: 500; color: rgb(129, 129, 152); line-height: 1.4; letter-spacing: 2px; margin-bottom: 15px; } .home-split .flx_1 p { font-size: 16px; font-weight: 400; color: rgb(129, 129, 152); line-height: 1.6; max-width: 360px; margin: 0 auto; } .home-split .flx_1 a { font-family: 'Montserrat'; font-size: 12px; font-weight: 500; color: #23a393; position: relative; display: inline-block; text-decoration: none; } .home-split .flx_2 h2 { font-size: 24px; font-weight: 700; color: rgba(255, 255, 255, 0.95); line-height: 1.2; letter-spacing: 0; margin-bottom: 20px; } .home-split .btm_block { position: relative; width: 100%; height: auto; } .home-split .block_inner { position: absolute; bottom: 0; left: 50px; } .home-split .block_txt { margin: 0 auto; } .home-split .block_txt p { font-size: 14px; font-weight: 500; color: #8f2c34; } @media only screen and (min-width: 767px) { .home-split .flx_1 { flex: 1.1 0 0; order: 1; flex-basis: 50%; transition: 0.3s ease-in; } .home-split .flx_2 { flex: 1 0 0; order: 2; flex-basis: 50%; transition: 0.3s ease-out; } } .split_footer { position: relative; bottom: 0; left: 0; right: 0; top: 0; } .split_footer p { font-size: 12px !important; } .split_footer ul { list-style-type: none; text-align: center; margin-bottom: 20px; position: absolute; bottom: 0; left: 0; right: 0; } .split_footer ul li { font-family: "Montserrat"; font-size: 16px; font-weight: 500; letter-spacing: 0; display: inline-block; margin-left: 10px; margin-right: 10px; } .split_footer ul li a { font-size: 14px; font-weight: 500; line-height: 24px; text-transform: uppercase; position: relative; display: inline-block; color: #FFFFFF; text-decoration: none; } .split_footer ul li a:hover { text-decoration: none; color: #97a6b5; } .split_footer ul li { margin-left: 8px; margin-right: 8px; } .split_footer ul li a img { width: 21px; vertical-align: middle; } ul#countdown { list-style: none; margin: 20px 0; padding: 0; display: inline-block; text-align: center; } ul#countdown li { display: inline-block; } @media only screen and (max-width: 767px) { ul#countdown li span { font-size: 34px; } .home-split .btn-alt { display: block; } } .prk-center { position: relative; left: 0; top: 0; bottom: 0; width: 100%; height: 100%; overflow: hidden; z-index: 1; display: table; } .prk-circle-box { display: table-cell; vertical-align: middle; text-align: center; } .prk-circle { margin: 0 auto; width: 270px; height: 270px; border-radius: 50%; background: #6892d5; box-shadow: 0 3px 50px 0 rgba(255, 94, 58, 0.1); color: #e84545; transition: all 400ms ease-in; } .prk-text { position: relative; left: 0; top: 0; bottom: 0; width: 100%; height: 100%; overflow: hidden; z-index: 1; display: table; } .txt-box { display: table-cell; vertical-align: middle; text-align: center; } .txt-box ul#countdown li span { font-family: Arial; font-size: 24px; font-weight: 600; line-height: 1; color: #FFFFFF; padding: 6px; transition: all 400ms ease-in; } .txt-box ul#countdown li.seperator { font-size: 21px; line-height: 1; vertical-align: top; color: #FFFFFF; transition: all 400ms ease-in; } .txt-box ul#countdown li p { color: rgba(255, 255, 255, 0.9); font-size: 11px; font-weight: 500; padding-top: 15px; transition: all 400ms ease-in; } .txt-box h2 { font-size: 16px; font-weight: 400; color: rgba(255, 255, 255, 0.8); margin-bottom: 0.5em; transition: all 400ms ease-in; } .txt-box h2 span { color: #FFFFFF; font-weight: 700; } .txt-box h3 { font-size: 16px; font-weight: 500; color: #e84545; margin-top: 0.5em; } .txt-box a { color: #6142d2; } .txt-box a:hover { color: #6142d2; } .chimp-form .mail { position: relative; background-color: #F9F9F9; box-shadow: none; -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; transition: all .3s; } .form { padding-top: 25px; position: relative; text-align: center; } .form input { color: #222222; font-family: 'Montserrat'; padding: 0; font-size: 13px; font-weight: 500; height: 45px; border: 0; border-radius: 30px; background-color: #efefef; outline: none; padding: 0 100px 0 20px; box-shadow: 0 5px 30px rgba(255, 255, 255, 0.1); } .form input:focus { outline: none !important; border-color: transparent; } .form .submit-button { height: 45px; border: 0; border-radius: 30px; margin-left: -35px; padding: 0 25px 0 25px; background: #047aed; font-family: 'Montserrat'; font-size: 14px; font-weight: 500; text-transform: capitalize; letter-spacing: 0; color: #FFFFFF; cursor: pointer; outline: none; box-shadow: 0 5px 30px rgba(255, 255, 255, 0.05); -webkit-transition: 500ms; -moz-transition: 500ms; transition: 500ms; } #email-error { position: absolute; left: 20%; right: 0; bottom: 0%; font-family: 'Montserrat'; font-size: 13px; font-weight: 500; color: #FF3333; } #response { position: absolute; left: 20%; bottom: -90%; vertical-align: middle; font-family: 'Montserrat'; font-size: 13px !important; font-weight: 500 !important; margin: 0 !important; color: #15ccbe; } #response h4 { font-size: 13px !important; font-weight: 500 !important; text-align: center; } .home-split #chimp-email-error { position: absolute; left: 0; bottom: -50%; vertical-align: middle; text-align: center; font-family: 'Montserrat'; font-size: 13px; font-weight: 500; color: #047aed; } .yd_cta_box #chimp-email-error { position: absolute; left: 25%; bottom: -50%; } .yd_cta_box #response { position: absolute; left: 25%; bottom: -90%; } .success-message { color:#047aed; margin-top: 5px; } .form-note p { font-size: 12px; } /*----------------------------------------------------------- ------------ Service Features Styling Starts --------------- -----------------------------------------------------------*/ .lbl-services { background: #FFFFFF; padding: 50px 0; position: relative; } .service-intro { padding: 0 0 50px 0; } .service-intro h1 { font-size: 32px; font-weight: 300; color: #364655; line-height: 1.2; margin-bottom: 20px; } .service-intro p { font-size: 16px; font-weight: 400; color: rgb(129, 129, 152); line-height: 1.6; max-width: 600px; margin: 0 auto; } .justify-center { justify-content: center !important; display: flex; } .lbl_cards { width: 100%; display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; border-radius: 10px; overflow: hidden; text-align: center; } /* .card_single { background: #FFFFFF; text-align: center; padding: 50px; margin: 0 0 50px 0; border-radius: 10px; cursor: pointer; -webkit-transition: 0.3s; transition: 0.3s; max-width: 360px; margin: 0 auto; border-bottom: 2px solid #e8f1f5; }*/ .card_single { width: 100%; margin: 1px; /*-webkit-flex-basis: 100%; -ms-flex-preferred-size: 100; flex-basis: 100%;*/ min-width: 200px; padding: 40px 32px; background-color: #FFF; -webkit-transition: 0.1s ease-in background-color; transition: 0.1s ease-in background-color; } /* .card_single:hover { box-shadow: 0 40px 60px 0 rgba(0, 0, 0, 0.1); } .card2 { border-bottom: 2px solid #fffce6; } .card3 { border-bottom: 2px solid #ffe9e6; }*/ .card-icon { width: 80px; height: 80px; margin: 0 auto; border-radius: 50%; background: rgba(30, 166, 154, 0.08); background: #edf4f7; display: table; text-align: center; } /* .card2 .card-icon { background: #fffce6; } .card3 .card-icon { background: #ffe9e6; } */ .card-img { display: table-cell; vertical-align: middle; text-align: center; } .card-text { padding: 15px; margin-top: 15px; } .card-text h3 { font-size: 21px; font-weight: 500; color: #364655; } .card-text p { font-size: 16px; font-weight: 500; color: rgb(129, 129, 152); line-height: 1.6; margin-top: 15px; } /*----------------------------------------------------- -------------- Flex Features Section 2 ---------------- -----------------------------------------------------*/ /*----------- Flex Features ------------*/ .flex-split { padding: 50px 0; background: #F7F7F8; } .flex-intro { margin-bottom: 50px; text-align: center; } .flex-intro h2 { font-size: 32px; font-weight: 300; color: #3a3a47; line-height: 1.3; margin-bottom: 10px; } .flex-intro p { font-size: 16px; font-weight: 500; line-height: 1.6; letter-spacing: 0.01em; color: #818198; max-width: 630px; margin: 0 auto; margin-top: 15px; margin-bottom: 15px; } .flex-inner { margin: 50px 0; } .flex-inner .f-image { text-align: center; } .flex-inner .f-text { padding: 0 35px; text-align: center; } .flex-inner .f-text { -webkit-box-flex:1; -ms-flex:1; flex:1; text-align: center; } .flex-inner .f-text h2 { font-size: 24px; font-weight: 600; color: #3a3a47; line-height: 1.3; margin-top: 25px; } .flex-inner .f-text p { font-size: 16px; font-weight: 400; line-height: 1.6; letter-spacing: 0.01em; color: #818198; margin-top: 15px; margin-bottom: 15px; } .flex-inner .f-text .left-content a { font-family: "Open Sans"; font-size: 14px; font-weight: 600; color: #047aed; position: relative; display: inline-block; text-decoration: none; } .flex-inner .f-text .left-content a::after { content: ""; position: absolute; left: 0; bottom: -20%; height: 1px; width: 50px; background: #8798ab; -webkit-transition: 0.3s; transition: 0.3s; } .flex-inner .f-text .left-content a:hover::after { width: 75px; } .flex-inner .f-text .left-content a:hover { text-decoration: none; } .flex-inner.flex-inverted .f-image { -webkit-box-ordinal-group:2; -ms-flex-order:1; order:1 } .flex-inner.flex-inverted .f-image img { -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ filter: grayscale(10%); } /*---------------- YD CTA Box Styling --------------------*/ .yd_cta_box { background: #FFFFFF; } .cta_box { background: #303c42 url(../images/motherboard1.png); background-repeat: no-repeat; background-position: right; border-radius: 0; display: flex; margin: auto; height: 480px; } .cta_box .cta_box_inner { margin: auto; padding: 0; } .cta_box .cta_box_inner .box_txt { padding: 0; } .cta_box .cta_box_inner h4 { font-size: 18px; font-weight: 400; line-height: 1.2; color: rgba(255, 255, 255, 0.8); margin-bottom: 20px; } .cta_box .cta_box_inner h2 { font-size: 24px; font-weight: 400; line-height: 1.6; color: #FFFFFF; } /* .cta_box .btn-action { background: #FFFFFF; color: #047aed; }*/ .yd_cta_sub { padding: 0; background: #303c42; text-align: center; } .cta_sub { display: flex; margin: auto; height: 480px; } .cta_sub .cta_sub_inner { margin: auto; padding: 0; } .cta_sub .cta_sub_inner h4 { font-size: 14px; font-weight: 400; line-height: 1.2; letter-spacing: 2px; text-transform: uppercase; color: rgba(255, 255, 255, 0.8); margin-bottom: 20px; } .cta_sub .cta_sub_inner h2 { font-size: 24px; font-weight: 300; line-height: 1.6; color: #FFFFFF; } /*------------------ YD Video Ft Section Styling --------------*/ .yd_boxed .yd_vid_ft { padding: 70px 0; } .yd_boxed .vid_box { padding: 50px; } .yd_vid_ft { background: #FFFFFF; padding: 50px 0; } .vid_box { min-height: 540px; width: 100%; background: rgb(4, 122, 237); background: #F7F7F8; border-radius: 1px; padding: 100px 50px; position: relative; } /* .vid_box::before { display: block; content: ""; width: 100%; height: 100%; position: absolute; left: 0; top: 0; background: rgba(0, 0, 0, 0.3); opacity: 1; z-index: 100; -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); -o-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -ms-backface-visibility: hidden; -o-backface-visibility: hidden; backface-visibility: hidden; }*/ .vid_intro { text-align: center; max-width: 580px; margin: 0 auto; } .vid_intro h2 { font-size: 34px; font-weight: 300; color: #3a3a47; line-height: 1.4; } .vid_intro p { font-size: 17px; font-weight: 500; color: rgb(129, 129, 152); line-height: 1.6; margin-top: 10px; } .yd_vid { text-align: center; max-width: 768px; margin: 0 auto; margin-top: 75px; } /*--------- Left Section Styling -----------*/ .yd_flx_tr { width: 100%; height: 100%; padding: 100px 0; } .yd_flx_tr_inner { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; -webkit-flex-flow: row wrap; justify-content: space-around; } .yd_flx_tr_inner > * { flex: 1 100%; } .left { background: #f3f3f5; padding: 75px; } .flx_content h2 { font-size: 24px; font-weight: 500; color: #202124; line-height: 1.4; } .flx_content h4 { font-size: 18px; font-weight: 500; color: #284184; line-height: 1.4; margin-top: 40px; } /*--------- Right Section Styling -----------*/ .right { background-color: #f3f3f5; background: #333; line-height: 0; position: relative; display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; -webkit-flex-flow: row wrap; justify-content: space-around; } .right > * { flex: 1 100%; } .right .sec-one { background-color: #ff524e; background-color: #f3f3f5; } .right .sec-two { background-color: #FFE16E; background-color: #C4FFDD; background-color: #ff524e; height: auto; padding: 50px; } .right .sec-one h2 , .right .sec-two h2 { font-size: 21px; font-weight: 600; color: #FFF; line-height: 1.2; } /*---------------------------------------- ------- Nav Tabs Styling Section --------- ----------------------------------------*/ .yd_tabs { padding: 50px 0; background: #f3f3f5; } .yd_tabs .nav-tabs { background-color: #f3f3f5; padding: 25px 0; border: none !important; } .yd_tabs .nav-tabs .nav-link { overflow: hidden; border-radius: 10px; padding: 25px; } .yd_tabs .nav-tabs .nav-link:hover { border-color: transparent; } .yd_tabs .nav-tabs .nav-link.active { background-color: #f3f3f5; color: #433434; border-color: transparent; position: relative; box-shadow:0 0px 50px rgba(0, 0, 0, .1); } .yd_tabs a { font-family: 'Montserrat'; font-size: 16px; font-weight: 500; line-height: 1.2; } .yd_tabs h5 { font-size: 32px; font-weight: 600; line-height: 1.2; margin-bottom: 20px; } .yd_tabs .nav-tabs h2 { font-size: 18px; font-weight: 600; line-height: 1.2; margin-bottom: 20px; padding: 30px; } .yd_tabs .nav-item { padding: 10px; } .yd_tabs p { font-size: 14px; font-weight: 500; line-height: 1.4; } .yd_tabs .ar-icon { float: left; width: 15%; } .yd_tabs .ar-icon img { margin-top: 0; vertical-align: middle; } .yd_tabs .ar-text { float: right; width: 85%; } .yd_tabs .ar-text h3 { font-size: 18px; color: #364655; font-weight: 600; line-height: 1.2; margin-bottom: 10px; } .yd_tabs .ar-text p { font-size: 14px; font-weight: 500; line-height: 1.6; color: #959597; } @media screen and (max-width: 576px) { .yd_tabs .nav-tabs { margin-bottom: 11px; } .yd_tabs .nav-tabs .nav-item { width: auto !important; } .yd_tabs .nav-tabs.yd_tabs-right { margin-bottom: 0; margin-top: 10px; } .yd_tabs .nav-tabs .nav-link { padding: 20px; } .yd_tabs .ar-icon { float: left; width: 25%; } .yd_tabs .ar-text { float: right; width: 75%; } } /*---------------------------------------------- ------------ Single Feature section ------------ ----------------------------------------------*/ .ft_single { position: relative; padding: 100px 0 100px 0; } .yd_flex { display: block; text-align: center; } .yd_flex_1 { -webkit-box-flex:1; -ms-flex:1; flex:1; } .yd_flex_2 { -webkit-box-ordinal-group:2; -ms-flex-order:1; order:1 } .yd_flex_1 .flex_content { max-width: 420px; margin: 0 auto; } .yd_flex_1 .flex_content h2 { font-size: 32px; font-weight: 700; line-height: 1.4; color: #364655; margin-bottom: 10px; } .yd_flex_1 .flex_content p { font-size: 16px; font-weight: 500; line-height: 1.4; color: #5f6368; margin-bottom: 5px; } .flex_main { display: flex; flex-wrap: wrap; justify-content: center; } .flex_sub { align-items: center; flex-basis: calc(50% - 24px); flex-direction: column; justify-content: center; margin: 10px; padding: 15px 10px; position: relative; background: rgb(248, 249, 250); border: 1px solid transparent; border-radius: 2px; display: flex; cursor: pointer; margin-bottom: 12px; transition: box-shadow .3s ease-in-out; } .flex_sub:hover { box-shadow:0 0 20px rgba(0, 0, 0, .1); } .flex_sub .sub_image { margin: 0px 0 20px; } .flex_sub .sub_image img { width: 60px; height: 60px; } .flex_sub .sub_text { text-align: center; } .flex_sub .sub_text h4 { font-size: 14px; font-weight: 600; line-height: 1.2; color: #202124; margin-bottom: 5px; } .flex_sub .sub_text p { font-size: 13px; font-weight: 500; line-height: 1.4; color: #5f6368; margin: 5px 0; } /*------------- YD Clients Styling ---------------*/ .yd_clients { padding: 50px 0; } .yd_clients_intro { text-align: center; max-width: 600px; margin: 0 auto; margin-bottom: 50px; } .yd_clients_intro h4 { font-size: 14px; font-weight: 500; color: #8798ab; line-height: 1.4; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 10px; } .yd_clients_intro h2 { font-size: 32px; font-weight: 300; color: #3a3a47; line-height: 1.4; margin-bottom: 15px; } .yd_clients_intro p { font-size: 18px; font-weight: 400; color: #787878; line-height: 1.6; letter-spacing: 0; } .flx_2 { max-width: 600px; margin: 0 auto; margin-top: 25px; text-align: center; } .yd_clients .flx_2 h1 { font-size: 26px; font-weight: 300; color: #3a3a47; line-height: 1.4; margin-bottom: 10px; } .yd_clients .flx_2 h2 { font-size: 16px; font-weight: 300; color: #3a3a47; line-height: 1.4; margin-bottom: 10px; } .yd_clients .flx_2 a { font-family: 'Open Sans'; font-size: 14px; font-weight: 400; color: #047aed; line-height: 1.4; text-decoration: none; } .yd_clients .review-img { display: flex; height: 100%; } .yd_clients .review-img .review-img-inner { margin: auto; } .yd_clients .review-img img { width: 120px; } .yd_clients .client-list { margin-top: 50px; } .yd_clients .client-list ul { list-style-type: none; text-align: center; } .yd_clients .client-list ul li { display: inline; margin: 0 20px; } .yd_clients .client-list ul li img { width: 100px; opacity: 0.6; cursor: pointer; } /*------------------------------------------------ --------------- Review Card Styling -------------- -------------------------------------------------*/ .yd_rev_slides { width: 100%; height: 100%; padding: 50px 0; background: #F7F7F8; background: linear-gradient(to right, rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0)), url(../images/bg.jpg) no-repeat center center; background-size: cover; background-position: top; } .rev_intro { display: flex; height: 100%; max-width: 360px; } .rev_intro_inner { margin: auto; } .rev_intro h4 { font-size: 14px; font-weight: 500; color: #8798ab; line-height: 1.4; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 10px; } .rev_intro h2 { font-size: 32px; font-weight: 300; color: #3a3a47; line-height: 1.4; margin-bottom: 15px; } .rev_intro p { font-size: 18px; font-weight: 400; color: #787878; line-height: 1.6; letter-spacing: 0; } .reviews { background: #FFFFFF; padding: 40px; } .rev_box { margin: 0 10px; background: #FFFFFF; } .rev_attr { display: flex; margin-top: 55px; } .rev_img { display: flex; } .rev_img .rev_img_inner { margin: auto; } .rev_img img { width: 50px; } .rev_footer { margin-left: 25px; } .rev_text h1 { font-size: 21px; font-weight: 500; color: #787878; line-height: 1.6; letter-spacing: 0; margin-bottom: 20px; } .rev_footer h2 { font-size: 18px; font-weight: 600; color: #565656; line-height: 1.4; margin-bottom: 2px; } .rev_footer h6 { font-size: 14px; font-weight: 500; color: #808080; line-height: 1.4; margin-bottom: 3px; } .rev_footer a { font-family: 'Montserrat'; font-size: 12px; font-weight: 500; color: #047aed; position: relative; display: inline-block; text-decoration: none; } /*----------------------------------------------------------- --------------------- Faq Section Styling ------------------- ------------------------------------------------------------*/ .yd_faqs { padding: 50px 0; /*background: #f7f7f8;*/ background: #fff; } .yd_faqs .yd_flx2 { min-height: 500px; display: flex; } .yd_faqs .yd_flx2 .flx_1 { line-height: normal; padding: 0 100px; min-height: 300px; } .yd_faqs .faq_intro { max-width: 600px; margin-bottom: 50px; } .yd_faqs .faq_inner h2 { font-size: 32px; font-weight: 300; color: #111; line-height: 1.2; margin-bottom: 15px; } .yd_faqs .faq_inner p { font-size: 16px; font-weight: 400; color: rgba(0, 0, 0, 0.5); line-height: 1.6; margin: 15px 0; } .yd_faqs .faq_inner h5 { font-size: 16px; font-weight: 400; color: rgba(0, 0, 0, 0.5); line-height: 1.6; margin-top: 20px; display: inline; } .yd_faqs .faq_inner a { display: inline; font-family: 'Open Sans'; color: #047aed; font-size: 14px; font-weight: 400; text-decoration: none; } #accordion .card-header:after { font-family: 'ionicons'; content: "\f209"; float: right; transition: 0.5s; } #accordion .card-header.collapsed:after { /* symbol for "collapsed" panels */ content: "\f218"; transition: 0.5s; } #accordion .card { border-radius: 0; border: 0; } #accordion .card-header { padding: 30px 0; background: #f7f7f8; border-bottom: 2px solid rgba(0,0,0,.03); cursor: pointer; } #accordion .card-header .card-title { font-family: 'Open Sans'; font-size: 18px; color: rgba(0, 0, 0, 0.75); font-weight: 500; line-height: 1.4; } #accordion .card-body { padding: 30px 0; background: #f7f7f8; } #accordion .card-body p { font-family: 'Roboto'; font-size: 16px; color: rgba(0, 0, 0, 0.6); font-weight: 400; line-height: 2; } .faq_alt { background: #FFFFFF; } .faq_alt #accordion .card-header { background: #FFFFFF; } .faq_alt #accordion .card-body { background: #FFFFFF; } /*----------------------------------------------------------- ------------------- Split Features Styling ------------------ ------------------------------------------------------------*/ .split_features { width: 100%; height: 100%; } .split_features .yd_flx2 { min-height: 500px; } .split_features .yd_flx2 .flx_1 { line-height: normal; background: #eee; padding: 0 100px; min-height: 300px; } .split_features .yd_flx2 .flx_1 .split_text { display: flex; margin: auto; height: 100%; } .split_features .yd_flx2 .flx_1 .split_text_inner { margin: auto; /* Important */ } .split_features .yd_flx2 .flx_1 h2 { font-family: 'Roboto' !important; font-size: 38px; font-weight: 300; color: #111; line-height: 1.4; margin-bottom: 15px; } .split_features .yd_flx2 .flx_1 p { font-family: 'Roboto' !important; font-size: 17px; font-weight: 400; color: #777; line-height: 1.6; margin-top: 15px; } .split_features .yd_flx2 .flx_2 { padding: 0; background: url(../images/hg.jpg) no-repeat center center; background-size: cover; min-height: 300px; } /*----------------------------------------------------------- ------------------- YD Custom Features Styling ------------------ ------------------------------------------------------------*/ .ft_cst2 { padding: 100px 0; background: #FFFFFF; } .ft_cst2 .split_text { display: flex; max-width: 480px; height: 100%; } .ft_cst2 .split_text_inner { margin: auto; /* Important */ } .ft_cst2 .split_text_inner h4 { font-size: 13px !important; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: #607d9c; line-height: 1.3; margin: 0 0 20px 0; } .ft_cst2 .split_text_inner h2 { font-size: 28px !important; font-weight: 300; color: #445555; line-height: 1.4; } .ft_cst2 .split_text_inner p { font-size: 18px !important; font-weight: 400 !important; color: rgba(0, 0, 0, 0.5); line-height: 1.6; margin: 20px 0 0 0; } .ft_cst2 .split_text_inner .btn-action { margin: 20px 0 0 0; } .ft_cst2 .f-text { line-height: normal; text-align: center; background: #FFFFFF; } .ft_cst2 .flex-inner { margin: 0; } .ft_cst2 .f-text { line-height: normal; text-align: center; background: #FFFFFF; margin-top: 0; } .ft_cst2 .flex-inner .f-image { margin-top: 50px !important; } /*----------------------------------------------------------- ------------------- YD Custom Features Styling ------------------ ------------------------------------------------------------*/ .yd_pricing { width: 100%; height: 100%; padding: 100px 0; background: #FFFFFF; } .yd_pricing .flex-inner { margin: 0; } .yd_pricing .flex-inner .f-text { margin-top: 50px; text-align: center; } .yd_pricing .split_text { display: flex; max-width: 600px; margin: 0 auto; height: 100%; } .yd_pricing .split_text_inner { margin: auto; /* Important */ } .yd_pricing .split_text_inner h4 { font-size: 14px; font-weight: 600; color: #8798ab; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 10px; } .yd_pricing .split_text_inner h2 { font-size: 34px !important; font-weight: 300; color: #445555; line-height: 1.3; } .yd_pricing .split_text_inner p { font-size: 16px !important; font-weight: 400 !important; color: rgba(0, 0, 0, 0.5); line-height: 1.6; margin-top: 20px; } .yd_pricing .split_text_inner h6 { font-size: 12px; font-weight: 400; color: #8798ab; display: inline; } .yd_pricing .btn-link { font-family: 'Open Sans'; color: #047aed; font-size: 13px; font-weight: 400; text-decoration: none; } .yd_pricing .split_text_inner .btn-action { margin: 20px 0 0 0; } .yd_pricing .f-text { line-height: normal; background: #FFFFFF; } .yd_pricing .prc_box { text-align: center; padding: 50px; max-width: 320px; margin: 0 auto; margin-bottom: 0; border-radius: 5px; background: #047aed; border: 1px solid #ececec; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); cursor: pointer; } .yd_pricing .prc_box .prc h1 { font-family: 'Roboto'; font-size: 48px; font-weight: 500; color: #F7F7F8; line-height: 1.4; } .yd_pricing .prc_box .prc span { font-family: 'Open Sans'; font-size: 14px; font-weight: 500; color: #F7F7F8; } .yd_pricing .prc_box .prc_text h2 { font-size: 24px; font-weight: 600; color: #F7F7F8; line-height: 1.6; margin-top: 20px; } .yd_pricing .prc_box p { font-size: 16px; font-weight: 400; color: rgba(255, 255, 255, 0.8); line-height: 1.6; margin-top: 10px; } /*---------------- Pricing Tables ------------------*/ /* ----- Pricing Tables Styling Starts ----- */ .pricing-section { width: 100%; height: 100%; padding-top: 100px; background: #F7F7F8; /* background: #FFFFFF;*/ } .pricing-intro h1 { font-size: 28px; color: #445555; font-weight: 300; line-height: 1.4; margin-bottom: 20px; } .pricing-intro p { font-size: 16px; font-weight: 500; line-height: 1.6; letter-spacing: 0.01em; color: #8B92AB; margin-top: 10px; margin-bottom: 50px; } .pricing-details { padding: 50px 0 0 0; } .pricing-section .table-left, .pricing-section .table-right { padding: 15%; margin: 0 auto; margin-bottom: 30px; /* background-color: #F7F7F8;*/ background-color: #fff; border: transparent; max-width: 400px; } .table-left h3, .table-right h3 { font-size: 25px; font-weight: 600; color: #505050; margin-bottom: 15px; } .table-left p, .table-right p { font-size: 14px; font-weight: 500; color: #505050; line-height: 1.4; } .pricing-section .table-right { color: #FFFFFF !important; background-color: #047aed; } .pricing-section .table-right h3, .pricing-section .table-right p { color: #FFFFFF !important; } .table-left .icon, .table-right .icon { padding: 50px 50px 40px 50px; } .table-left .icon img, .table-right .icon img { width: 60px; height: 60px; margin: 0 auto; } .table-left .pricing-details span, .table-right .pricing-details span { display: inline-block; font-family: 'Open Sans'; font-size: 42px; font-weight: 500; color: #505050; margin-bottom: 15px; } .sub_note { font-family: 'Open Sans'; padding-bottom:5px; font-size: 10px; font-weight: 400; color: #505050; } .sub_deposit { font-family: 'Open Sans'; padding-bottom:5px; font-size: 10px; font-weight: 400; color: #505050; } .sub_span { font-family: 'Open Sans'; padding-top:10px; font-size: 15px; font-weight: 400; color: #505050; } .sub_span_alt { color: #FFFFFF; } .table-left .pricing-details h2, .table-right .pricing-details h2 { font-size: 21px; font-weight: 500; color: #505050; margin-bottom: 30px; } .table-left .pricing-details p, .table-right .pricing-details p { font-size: 14px; font-weight: 300; color: #505050; letter-spacing: 1px; line-height: 1.4; } .table-right .pricing-details h2, .table-right .pricing-details span { color: #FFFFFF !important; } .pricing-section .table-left, .pricing-section .table-right { margin-top: 20px; } .pricing-section .table-center { margin-top: 0; } .pricing-section .btn-action { background-color: #4285f4; border-color: #4285f4; } .btn-white { color: #047aed; background-color: #FFFFFF !important; border-color: #FFFFFF !important; } .pricing-section .btn-white:hover { color: #555da8; } .pricing-section .refund-txt { font-size: 12px; font-weight: 500; color: #505050; } /* ------------ Bact-to-Top Styling Starts Here ------------*/ .footer { background: #040E1A; background: #303c42; padding: 35px 0; border-top: 2px solid rgba(255, 255, 255, 0.1); } .footer-inner { background: #1e266d; } .footer .footer-logo { text-align: center; } .footer .footer-logo h2 { font-size: 18px; font-weight: 500; color: #e5e5e5; text-transform: uppercase; line-height: 1.4; letter-spacing: 1px; } .footer p { font-size: 24px; font-weight: 700; color: #e5e5e5; line-height: 1.4; margin-top: 0; } /* ------------ Bact-to-Top Styling Starts Here ------------*/ .back-to-top { position: fixed; bottom: 30px; right: 30px; font-family: 'Montserrat'; font-size: 11px; font-weight: 600; text-transform: uppercase; color: #FFFFFF; line-height: 1.4; display: inline-block; padding: 1.2rem 1.2rem; margin-top: 0; border-radius: 0; background: url(../icons/up.png) center top 0.5rem no-repeat #047aed; text-decoration: none; -webkit-transition: 200ms; -moz-transition: 200ms; -o-transition: 200ms; transition: 200ms; } .back-to-top:hover { color: #FFFFFF; text-decoration: none; } .footer ul { list-style-type: none; text-align: center; margin-top: 0; } .footer .footer-menu { text-align: center; margin: 20px 0; } .footer .footer-links ul { text-align: center; } .footer ul li { font-family: "Montserrat"; font-size: 16px; font-weight: 500; letter-spacing: 0; display: inline-block; margin-left: 10px; margin-right: 10px; } .footer ul li a { font-size: 14px; font-weight: 500; line-height: 24px; text-transform: uppercase; position: relative; display: inline-block; color: #FFFFFF; text-decoration: none; } .footer ul li a:hover { text-decoration: none; color: #97a6b5; } .footer .footer-links ul li { margin-left: 10px; margin-right: 10px; } .footer .footer-links ul li a img { width: 24px; vertical-align: middle; padding-right:5px; } /*------------------------------------------ -------------- Media Queries --------------- ------------------------------------------*/ @media only screen and (min-width: 767px) { .logo .tld { background: #047aed; } .navbar-nav { margin-top: 0; } .navbar-nav .nav-item { margin-top: 0; margin-right: 15px; font-size: 0.85rem; font-weight: 400; } .btn-nav { margin: 0; } .container-s { padding-left: 20px; padding-right: 20px; } .container-m { padding-left: 20px; padding-right: 20px; } .hero-inner h2 { font-size: 38px; } .home .hero-content { padding: 75px 0 0 0; } .home-2 { padding: 150px 0 50px 0; } .home-2 .hero-form #contactForm { float: right; } .home-2 .hero-content .hero-content-inner { text-align: left; } .home-3 .hero-content .hero-content-inner { text-align: left; } .home-3 .hero-img img { box-shadow: 0 0px 30px rgba(0, 0, 0, 0.2); } .lbl-services { padding: 100px 0 50px 0; } .service-intro h1 { font-size: 34px; } .card_single { width: calc(33.333% - 2px); } .flex-split { padding: 100px 0; } .flex-intro h2 { font-size: 34px; } .flex-inner { position: relative; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; margin: 75px 0 0 0; } .flex-inner .f-image { -webkit-box-flex: 0; -ms-flex: 0 0 400px; flex: 0 0 400px; } .ft_cst2 .split_text_inner h2 { font-size: 34px !important; } .yd_pricing .flex-inner .f-text { margin-top: 0; text-align: left; } .yd_clients { padding: 100px 0; } .yd_rev_slides { padding: 100px 0; } .rev_text h1 { font-size: 24px; } .reviews { padding: 50px; } .cta_box .cta_box_inner h2 { font-size: 32px; } .cta_sub .cta_sub_inner h2 { font-size: 32px; } .ft_cst2 .flex-inner .f-image { margin-top: 0 !important; -webkit-box-flex: 0; -ms-flex: 0 0 400px; flex: 0 0 400px; } .yd_vid_ft { padding: 100px 0; } .yd_pricing .f-image { -webkit-box-flex: 0; -ms-flex: 0 0 360px; flex: 0 0 360px; } .yd_pricing .split_text_inner h2 { font-size: 34px !important; } .yd_pricing .split_text_inner p { font-size: 16px !important; } .yd_pricing .prc_box { margin-bottom: 0; } .flex-inner .f-text h2 { margin-top: 0; } .yd_faqs { padding: 100px 0; } .yd_tabs { padding: 100px 0; } .yd_pricing .split_text_inner h2 { font-size: 34px !important; } .yd_pricing .split_text_inner p { font-size: 18px !important; } .yd_pricing .prc_box { margin-bottom: 0; } .pricing-intro h1 { font-size: 34px; } .flex-inner .f-text h2 { font-size: 24px; } .flex-inner .f-text p { font-size: 16px; font-weight: 500; } .yd_flex { position: relative; display: -webkit-box; display: -ms-flexbox; display: flex; text-align: left; -webkit-box-align: center; -ms-flex-align: center; align-items: center; } .yd_flex .yd_flex_2 { -webkit-box-flex: 0; -ms-flex: 0 0 568px; flex: 0 0 568px; } .yd_flex_1 .flex_content { max-width: 420px; margin: 0; } .left { padding: 50px; } .flx_content h2 { font-size: 24px; } .flex_main { display: flex; } .flex_sub { margin: 12px; padding: 32px; } .flex_sub .sub_text h4 { font-size: 16px; } .left { flex: 1 0 0; } .right { flex: 1 0 0; flex-direction: column; } .right .sec-one { flex: 1; } .right .sec-two { flex: 1; } .right .sec-two { padding: 25px; } .flx_2 { margin-top: 25px; text-align: center; } .yd_clients .review-img img { width: 120px; } .split_features .yd_flx2 .flx_1 { flex: 1 0 0; flex-basis: 50%; } .split_features .yd_flx2 .flx_2 { flex: 1 0 0; flex-basis: 50%; } .yd_faqs .yd_flx2 .flx_1 { flex: 1 0 0; flex-basis: 50%; } .yd_faqs .yd_flx2 .flx_2 { flex: 1 0 0; flex-basis: 50%; } .footer .footer-logo { text-align: left; } .footer .footer-menu { text-align: right; margin: 0; } .footer .footer-links ul { text-align: right; } } @media only screen and (min-width: 480px) { .container-s { padding-left: 40px; padding-right: 40px; } .flex-inner .f-text { -webkit-box-flex: 1; -ms-flex: 1; flex: 1; text-align: left; } } @media only screen and (min-width: 24em) { .prk-circle { width: 360px; height: 360px; } .txt-box h2 { font-size: 21px; font-weight: 500; } .txt-box .btn-action { padding: 10px 24px; font-size: 12px; margin: 25px 10px 0 0; } .txt-box ul#countdown li span { font-size: 38px; } .txt-box ul#countdown li.seperator { font-size: 32px; } .txt-box ul#countdown li p { font-size: 14px; } } @media only screen and (min-width: 768px) and (max-width: 1024px) { .card_single { text-align: center; padding: 10px; } .yd_flex .yd_flex_2 { -webkit-box-flex: 0; -ms-flex: 0 0 420px; flex: 0 0 420px; } .home-split .flx_2 { min-width: 100%; } } @media only screen and (min-width: 1024px) { .home-3 .hero-img img { box-shadow: 0 0px 30px rgba(0, 0, 0, 0.2); } .home-split .flx_2 { min-width: inherit; } .ft_cst2 .flex-inner .f-image { margin-top: 0 !important; -webkit-box-flex: 0; -ms-flex: 0 0 540px; flex: 0 0 540px; } .cta_box .cta_box_inner { padding: 0; } .yd_pricing .f-image { -webkit-box-flex: 0; -ms-flex: 0 0 540px; flex: 0 0 540px; } .left { padding: 100px; } .right .sec-two { padding: 50px; } .flx_content h2 { font-size: 30px; } .yd_tabs .ar-icon { float: left; width: 20%; } .yd_tabs .ar-text { float: right; width: 80%; } } @media only screen and (max-width: 420px) { .form { text-align: center; } .form input { padding: 0 75px 0 20px; } .form .submit-button { margin-left: 0; margin-top: 10px; } #chimp-email-error { left: 5%; bottom: -30%; } .yd_cta_box #response { position: absolute; left: 5%; bottom: -50%; } .yd_cta_box #chimp-email-error { left: 15%; bottom: -25%; } } .team-image { padding: 0px; } .laptimer-text { padding: 15px; margin-top: 5px; } .laptimer-text h3 { font-size: 21px; font-weight: 500; text-align:center; color: #364655; } #interface .img-fluid { margin-bottom:0px !important; } .laptimer-text p { text-align:center; font-size: 16px; font-weight: 500; color: rgb(129, 129, 152); line-height: 1.6; margin-top: 15px; } .team-text { padding: 15px; margin-top: 15px; } .team-text h3 { font-size: 21px; font-weight: 500; text-align:center; color: #364655; } .team-text p { text-align:center; font-size: 16px; font-weight: 500; color: rgb(129, 129, 152); line-height: 1.6; margin-top: 15px; } .team { padding: 50px 0; position: relative; } .questions { text-align:center; font-size: 14px; font-weight: 500; line-height: 1.6; letter-spacing: 0.01em; color: #818198; max-width: 630px; margin: 0 auto; margin-top: 15px; margin-bottom: 15px; } .listofbikes h4 { font-size: 14px; font-weight: 500; color: rgba(0, 0, 0, 0.4); line-height: 1.2; letter-spacing: 1px; margin-bottom: 10px; } .img-fluid { box-shadow: 0 0px 30px rgba(0, 0, 0, 0.2); } .logos { max-width: 100%; height: auto; } .loop-video { margin-top: 25px; } .cornering-shot { display:block !important; } .track-info { font-family: "Open Sans"; font-size: 14px; font-weight: 600; margin-bottom:25px; color: #047aed; position: relative; display: inline-block; text-decoration: none; } .track-info::after { content: ""; position: absolute; left: 0; bottom: -20%; height: 1px; width: 50px; background: #8798ab; -webkit-transition: 0.3s; transition: 0.3s; } .track-info:hover::after { width: 75px; } .bike-fluid { max-width: 100%; height: auto; } .drone-video { box-shadow: 0 0px 30px rgba(0, 0, 0, 0.2); } #contact a { color: #fff; font-weight:600; } .reserving h1 { text-align:left; } .signup-spacing { padding-top:60px; } .fillout { text-align:left; } .fillout label { padding-bottom:10px; } .ack-txt { color: rgba(0, 0, 0, 0.5) !important; font-weight:400 !important; text-align:left; } .ack { display:block; } .row { margin:0 !important; } .container-fluid { padding:0 !important; } .navbar-logo { height:2.5em; } .hero-content-inner p b { font-weight: 700; } .payment .row { margin-top:50px !important; } .announcement { font-weight:600; margin:1em; } .orderbutton { font-family: "Open Sans"; font-size: 14px; font-weight: 600; color: #047aed; position: relative; display: inline-block; text-decoration: none; } .twitchiframe { min-height: 300px; width: 100%; height: 100%; top: 0; } .track { font-weight: 300; font-size:12px; padding-bottom:20px; line-height:1.5em; } .tracklink { font-size:14px; font-weight:700; padding-top:10px; } .textlink { font-weight:600; text-decoration: underline; } .trackrow { padding-bottom:20px; padding-top:20px; } .what-alt { background: #F7F7F8; } .markdown-body { box-sizing: border-box; min-width: 200px; max-width: 980px; margin: 0 auto; padding: 45px; } @media (max-width: 767px) { .markdown-body { padding: 15px; } } .code-example { margin: 0; padding: 0; margin-bottom: 0px !important; background-color: transparent !important; } .copyright { color: #fff !important; font-size:13px; padding-bottom: 12px; } a.trademarks { color: #fff !important; font-weight:600; display: inline-block; text-decoration: underline; padding: 0.8rem 0; } .highlight { background: #fff !important; } ================================================ FILE: docs/assets/favicons/browserconfig.xml ================================================ #da532c ================================================ FILE: docs/assets/favicons/site.webmanifest ================================================ { "name": "", "short_name": "", "icons": [ { "src": "/assets/favicons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/assets/favicons/android-chrome-256x256.png", "sizes": "256x256", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" } ================================================ FILE: docs/assets/js/contact.js ================================================ $(function () { "use strict"; $('#contact-form').validator(); $('#contact-form').on('submit', function (e) { if (!e.isDefaultPrevented()) { var url = "assets/php/contact.php"; $.ajax({ type: "POST", url: url, data: $(this).serialize(), success: function (data) { var messageAlert = 'alert-' + data.type; var messageText = data.message; var alertBox = '
' + messageText + '
'; if (messageAlert && messageText) { $('#contact-form').find('.messages').html(alertBox); $('#contact-form')[0].reset(); } } }); return false; } }) }); ================================================ FILE: docs/assets/js/custom.js ================================================ // Custom Scripts for Primal Template // jQuery(function($) { "use strict"; // get the value of the bottom of the #main element by adding the offset of that element plus its height, set it as a variable var mainbottom = $('#main').offset().top; // on scroll, $(window).on('scroll',function(){ // we round here to reduce a little workload stop = Math.round($(window).scrollTop()); if (stop > mainbottom) { $('.navbar').addClass('past-main'); $('.navbar').addClass('effect-main') } else { $('.navbar').removeClass('past-main'); } }); // Collapse navbar on click $(document).on('click.nav','.navbar-collapse.in',function(e) { if( $(e.target).is('a') ) { $(this).removeClass('in').addClass('collapse'); } }); /*----------------------------------- ----------- Scroll To Top ----------- ------------------------------------*/ $(window).scroll(function () { if ($(this).scrollTop() > 1000) { $('#back-top').fadeIn(); } else { $('#back-top').fadeOut(); } }); // scroll body to 0px on click $('#back-top').on('click', function () { $('#back-top').tooltip('hide'); $('body,html').animate({ scrollTop: 0 }, 1500); return false; }); /*-------- Owl Carousel ---------- */ $(".reviews").owlCarousel({ slideSpeed : 200, items: 1, singleItem: true, autoPlay : true, pagination : false }); /*-------- Owl Carousel ---------- */ $(".review-cards").owlCarousel({ slideSpeed : 200, items: 1, singleItem: true, autoPlay : true, pagination : false }); /* ------ jQuery for Easing min -- */ // Smooth scrolling using jQuery easing $('a.js-scroll-trigger[href*="#"]:not([href="#"])').on('click', function () { if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { var target = $(this.hash); target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); if (target.length) { $('html, body').animate({ scrollTop: (target.offset().top - 54) }, 1000, "easeInOutExpo"); return false; } } }); /* --------- Wow Init ------ */ new WOW().init(); /* ------ Countdown ----- */ $('#countdown').countdown({ date: '12/12/2031 12:00:00', offset: +2, day: 'Day', days: 'Days' }, function () { alert('Done!'); }); /*----- Preloader ----- */ $(window).load(function() { setTimeout(function() { $('#loading').fadeOut('slow', function() { }); }, 3000); }); /*----- Subscription Form ----- */ $(document).ready(function() { // jQuery Validation $("#chimp-form").validate({ // if valid, post data via AJAX submitHandler: function(form) { $.post("assets/php/subscribe.php", { email: $("#email").val() }, function(data) { $('#response').html(data); }); }, // all fields are required rules: { email: { required: true, email: true } } }); }); // Accordion // function toggleChevron(e) { $(e.target) .prev('.panel-heading') .find("span.glyphicon") .toggleClass('glyphicon-chevron-down glyphicon-chevron-right'); } $('#accordion').on('hide.bs.collapse show.bs.collapse', toggleChevron); }); ================================================ FILE: docs/assets/js/jquery-2.1.1.js ================================================ /*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) },_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery";return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s);e.click(function(){b.prev()}),f.click(function(){b.next()}),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),A()}); // Generated by CoffeeScript 1.6.2 /* jQuery Waypoints - v2.0.3 Copyright (c) 2011-2013 Caleb Troughton Dual licensed under the MIT license and GPL license. https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt */ (function(){var t=[].indexOf||function(t){for(var e=0,n=this.length;e=0;s={horizontal:{},vertical:{}};f=1;a={};u="waypoints-context-id";p="resize.waypoints";y="scroll.waypoints";v=1;w="waypoints-waypoint-ids";g="waypoint";m="waypoints";o=function(){function t(t){var e=this;this.$element=t;this.element=t[0];this.didResize=false;this.didScroll=false;this.id="context"+f++;this.oldScroll={x:t.scrollLeft(),y:t.scrollTop()};this.waypoints={horizontal:{},vertical:{}};t.data(u,this.id);a[this.id]=this;t.bind(y,function(){var t;if(!(e.didScroll||c)){e.didScroll=true;t=function(){e.doScroll();return e.didScroll=false};return r.setTimeout(t,n[m].settings.scrollThrottle)}});t.bind(p,function(){var t;if(!e.didResize){e.didResize=true;t=function(){n[m]("refresh");return e.didResize=false};return r.setTimeout(t,n[m].settings.resizeThrottle)}})}t.prototype.doScroll=function(){var t,e=this;t={horizontal:{newScroll:this.$element.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.$element.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};if(c&&(!t.vertical.oldScroll||!t.vertical.newScroll)){n[m]("refresh")}n.each(t,function(t,r){var i,o,l;l=[];o=r.newScroll>r.oldScroll;i=o?r.forward:r.backward;n.each(e.waypoints[t],function(t,e){var n,i;if(r.oldScroll<(n=e.offset)&&n<=r.newScroll){return l.push(e)}else if(r.newScroll<(i=e.offset)&&i<=r.oldScroll){return l.push(e)}});l.sort(function(t,e){return t.offset-e.offset});if(!o){l.reverse()}return n.each(l,function(t,e){if(e.options.continuous||t===l.length-1){return e.trigger([i])}})});return this.oldScroll={x:t.horizontal.newScroll,y:t.vertical.newScroll}};t.prototype.refresh=function(){var t,e,r,i=this;r=n.isWindow(this.element);e=this.$element.offset();this.doScroll();t={horizontal:{contextOffset:r?0:e.left,contextScroll:r?0:this.oldScroll.x,contextDimension:this.$element.width(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:r?0:e.top,contextScroll:r?0:this.oldScroll.y,contextDimension:r?n[m]("viewportHeight"):this.$element.height(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};return n.each(t,function(t,e){return n.each(i.waypoints[t],function(t,r){var i,o,l,s,f;i=r.options.offset;l=r.offset;o=n.isWindow(r.element)?0:r.$element.offset()[e.offsetProp];if(n.isFunction(i)){i=i.apply(r.element)}else if(typeof i==="string"){i=parseFloat(i);if(r.options.offset.indexOf("%")>-1){i=Math.ceil(e.contextDimension*i/100)}}r.offset=o-e.contextOffset+e.contextScroll-i;if(r.options.onlyOnScroll&&l!=null||!r.enabled){return}if(l!==null&&l<(s=e.oldScroll)&&s<=r.offset){return r.trigger([e.backward])}else if(l!==null&&l>(f=e.oldScroll)&&f>=r.offset){return r.trigger([e.forward])}else if(l===null&&e.oldScroll>=r.offset){return r.trigger([e.forward])}})})};t.prototype.checkEmpty=function(){if(n.isEmptyObject(this.waypoints.horizontal)&&n.isEmptyObject(this.waypoints.vertical)){this.$element.unbind([p,y].join(" "));return delete a[this.id]}};return t}();l=function(){function t(t,e,r){var i,o;r=n.extend({},n.fn[g].defaults,r);if(r.offset==="bottom-in-view"){r.offset=function(){var t;t=n[m]("viewportHeight");if(!n.isWindow(e.element)){t=e.$element.height()}return t-n(this).outerHeight()}}this.$element=t;this.element=t[0];this.axis=r.horizontal?"horizontal":"vertical";this.callback=r.handler;this.context=e;this.enabled=r.enabled;this.id="waypoints"+v++;this.offset=null;this.options=r;e.waypoints[this.axis][this.id]=this;s[this.axis][this.id]=this;i=(o=t.data(w))!=null?o:[];i.push(this.id);t.data(w,i)}t.prototype.trigger=function(t){if(!this.enabled){return}if(this.callback!=null){this.callback.apply(this.element,t)}if(this.options.triggerOnce){return this.destroy()}};t.prototype.disable=function(){return this.enabled=false};t.prototype.enable=function(){this.context.refresh();return this.enabled=true};t.prototype.destroy=function(){delete s[this.axis][this.id];delete this.context.waypoints[this.axis][this.id];return this.context.checkEmpty()};t.getWaypointsByElement=function(t){var e,r;r=n(t).data(w);if(!r){return[]}e=n.extend({},s.horizontal,s.vertical);return n.map(r,function(t){return e[t]})};return t}();d={init:function(t,e){var r;if(e==null){e={}}if((r=e.handler)==null){e.handler=t}this.each(function(){var t,r,i,s;t=n(this);i=(s=e.context)!=null?s:n.fn[g].defaults.context;if(!n.isWindow(i)){i=t.closest(i)}i=n(i);r=a[i.data(u)];if(!r){r=new o(i)}return new l(t,r,e)});n[m]("refresh");return this},disable:function(){return d._invoke(this,"disable")},enable:function(){return d._invoke(this,"enable")},destroy:function(){return d._invoke(this,"destroy")},prev:function(t,e){return d._traverse.call(this,t,e,function(t,e,n){if(e>0){return t.push(n[e-1])}})},next:function(t,e){return d._traverse.call(this,t,e,function(t,e,n){if(et.oldScroll.y})},left:function(t){if(t==null){t=r}return h._filter(t,"horizontal",function(t,e){return e.offset<=t.oldScroll.x})},right:function(t){if(t==null){t=r}return h._filter(t,"horizontal",function(t,e){return e.offset>t.oldScroll.x})},enable:function(){return h._invoke("enable")},disable:function(){return h._invoke("disable")},destroy:function(){return h._invoke("destroy")},extendFn:function(t,e){return d[t]=e},_invoke:function(t){var e;e=n.extend({},s.vertical,s.horizontal);return n.each(e,function(e,n){n[t]();return true})},_filter:function(t,e,r){var i,o;i=a[n(t).data(u)];if(!i){return[]}o=[];n.each(i.waypoints[e],function(t,e){if(r(i,e)){return o.push(e)}});o.sort(function(t,e){return t.offset-e.offset});return n.map(o,function(t){return t.element})}};n[m]=function(){var t,n;n=arguments[0],t=2<=arguments.length?e.call(arguments,1):[];if(h[n]){return h[n].apply(null,t)}else{return h.aggregate.call(null,n)}};n[m].settings={resizeThrottle:100,scrollThrottle:30};return i.load(function(){return n[m]("refresh")})})}).call(this); /*! * imagesLoaded PACKAGED v4.1.1 * JavaScript is all like "You images are done yet or what?" * MIT License */ !function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=0,o=i[n];e=e||[];for(var r=this._onceEvents&&this._onceEvents[t];o;){var s=r&&r[o];s&&(this.off(t,o),delete r[o]),o.apply(this,e),n+=s?0:1,o=i[n]}return this}},t}),function(t,e){"use strict";"function"==typeof define&&define.amd?define(["ev-emitter/ev-emitter"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}(window,function(t,e){function i(t,e){for(var i in e)t[i]=e[i];return t}function n(t){var e=[];if(Array.isArray(t))e=t;else if("number"==typeof t.length)for(var i=0;ih;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}return this.each(function(){var d=a.data(this,b);d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d))})}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;"function"==typeof define&&define.amd?define("jquery-bridget/jquery.bridget",["jquery"],c):c("object"==typeof exports?require("jquery"):a.jQuery)}(window),function(a){function b(b){var c=a.event;return c.target=c.target||c.srcElement||b,c}var c=document.documentElement,d=function(){};c.addEventListener?d=function(a,b,c){a.addEventListener(b,c,!1)}:c.attachEvent&&(d=function(a,c,d){a[c+d]=d.handleEvent?function(){var c=b(a);d.handleEvent.call(d,c)}:function(){var c=b(a);d.call(a,c)},a.attachEvent("on"+c,a[c+d])});var e=function(){};c.removeEventListener?e=function(a,b,c){a.removeEventListener(b,c,!1)}:c.detachEvent&&(e=function(a,b,c){a.detachEvent("on"+b,a[b+c]);try{delete a[b+c]}catch(d){a[b+c]=void 0}});var f={bind:d,unbind:e};"function"==typeof define&&define.amd?define("eventie/eventie",f):"object"==typeof exports?module.exports=f:a.eventie=f}(window),function(){"use strict";function a(){}function b(a,b){for(var c=a.length;c--;)if(a[c].listener===b)return c;return-1}function c(a){return function(){return this[a].apply(this,arguments)}}var d=a.prototype,e=this,f=e.EventEmitter;d.getListeners=function(a){var b,c,d=this._getEvents();if(a instanceof RegExp){b={};for(c in d)d.hasOwnProperty(c)&&a.test(c)&&(b[c]=d[c])}else b=d[a]||(d[a]=[]);return b},d.flattenListeners=function(a){var b,c=[];for(b=0;be;e++)if(b=c[e]+a,"string"==typeof d[b])return b}}var c="Webkit Moz ms Ms O".split(" "),d=document.documentElement.style;"function"==typeof define&&define.amd?define("get-style-property/get-style-property",[],function(){return b}):"object"==typeof exports?module.exports=b:a.getStyleProperty=b}(window),function(a,b){function c(a){var b=parseFloat(a),c=-1===a.indexOf("%")&&!isNaN(b);return c&&b}function d(){}function e(){for(var a={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},b=0,c=h.length;c>b;b++){var d=h[b];a[d]=0}return a}function f(b){function d(){if(!m){m=!0;var d=a.getComputedStyle;if(j=function(){var a=d?function(a){return d(a,null)}:function(a){return a.currentStyle};return function(b){var c=a(b);return c||g("Style returned "+c+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),c}}(),k=b("boxSizing")){var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style[k]="border-box";var f=document.body||document.documentElement;f.appendChild(e);var h=j(e);l=200===c(h.width),f.removeChild(e)}}}function f(a){if(d(),"string"==typeof a&&(a=document.querySelector(a)),a&&"object"==typeof a&&a.nodeType){var b=j(a);if("none"===b.display)return e();var f={};f.width=a.offsetWidth,f.height=a.offsetHeight;for(var g=f.isBorderBox=!(!k||!b[k]||"border-box"!==b[k]),m=0,n=h.length;n>m;m++){var o=h[m],p=b[o];p=i(a,p);var q=parseFloat(p);f[o]=isNaN(q)?0:q}var r=f.paddingLeft+f.paddingRight,s=f.paddingTop+f.paddingBottom,t=f.marginLeft+f.marginRight,u=f.marginTop+f.marginBottom,v=f.borderLeftWidth+f.borderRightWidth,w=f.borderTopWidth+f.borderBottomWidth,x=g&&l,y=c(b.width);y!==!1&&(f.width=y+(x?0:r+v));var z=c(b.height);return z!==!1&&(f.height=z+(x?0:s+w)),f.innerWidth=f.width-(r+v),f.innerHeight=f.height-(s+w),f.outerWidth=f.width+t,f.outerHeight=f.height+u,f}}function i(b,c){if(a.getComputedStyle||-1===c.indexOf("%"))return c;var d=b.style,e=d.left,f=b.runtimeStyle,g=f&&f.left;return g&&(f.left=b.currentStyle.left),d.left=c,c=d.pixelLeft,d.left=e,g&&(f.left=g),c}var j,k,l,m=!1;return f}var g="undefined"==typeof console?d:function(a){console.error(a)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"];"function"==typeof define&&define.amd?define("get-size/get-size",["get-style-property/get-style-property"],f):"object"==typeof exports?module.exports=f(require("desandro-get-style-property")):a.getSize=f(a.getStyleProperty)}(window),function(a){function b(a){"function"==typeof a&&(b.isReady?a():g.push(a))}function c(a){var c="readystatechange"===a.type&&"complete"!==f.readyState;b.isReady||c||d()}function d(){b.isReady=!0;for(var a=0,c=g.length;c>a;a++){var d=g[a];d()}}function e(e){return"complete"===f.readyState?d():(e.bind(f,"DOMContentLoaded",c),e.bind(f,"readystatechange",c),e.bind(a,"load",c)),b}var f=a.document,g=[];b.isReady=!1,"function"==typeof define&&define.amd?define("doc-ready/doc-ready",["eventie/eventie"],e):"object"==typeof exports?module.exports=e(require("eventie")):a.docReady=e(a.eventie)}(window),function(a){"use strict";function b(a,b){return a[g](b)}function c(a){if(!a.parentNode){var b=document.createDocumentFragment();b.appendChild(a)}}function d(a,b){c(a);for(var d=a.parentNode.querySelectorAll(b),e=0,f=d.length;f>e;e++)if(d[e]===a)return!0;return!1}function e(a,d){return c(a),b(a,d)}var f,g=function(){if(a.matches)return"matches";if(a.matchesSelector)return"matchesSelector";for(var b=["webkit","moz","ms","o"],c=0,d=b.length;d>c;c++){var e=b[c],f=e+"MatchesSelector";if(a[f])return f}}();if(g){var h=document.createElement("div"),i=b(h,"div");f=i?b:e}else f=d;"function"==typeof define&&define.amd?define("matches-selector/matches-selector",[],function(){return f}):"object"==typeof exports?module.exports=f:window.matchesSelector=f}(Element.prototype),function(a,b){"use strict";"function"==typeof define&&define.amd?define("fizzy-ui-utils/utils",["doc-ready/doc-ready","matches-selector/matches-selector"],function(c,d){return b(a,c,d)}):"object"==typeof exports?module.exports=b(a,require("doc-ready"),require("desandro-matches-selector")):a.fizzyUIUtils=b(a,a.docReady,a.matchesSelector)}(window,function(a,b,c){var d={};d.extend=function(a,b){for(var c in b)a[c]=b[c];return a},d.modulo=function(a,b){return(a%b+b)%b};var e=Object.prototype.toString;d.isArray=function(a){return"[object Array]"==e.call(a)},d.makeArray=function(a){var b=[];if(d.isArray(a))b=a;else if(a&&"number"==typeof a.length)for(var c=0,e=a.length;e>c;c++)b.push(a[c]);else b.push(a);return b},d.indexOf=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},d.removeFrom=function(a,b){var c=d.indexOf(a,b);-1!=c&&a.splice(c,1)},d.isElement="function"==typeof HTMLElement||"object"==typeof HTMLElement?function(a){return a instanceof HTMLElement}:function(a){return a&&"object"==typeof a&&1==a.nodeType&&"string"==typeof a.nodeName},d.setText=function(){function a(a,c){b=b||(void 0!==document.documentElement.textContent?"textContent":"innerText"),a[b]=c}var b;return a}(),d.getParent=function(a,b){for(;a!=document.body;)if(a=a.parentNode,c(a,b))return a},d.getQueryElement=function(a){return"string"==typeof a?document.querySelector(a):a},d.handleEvent=function(a){var b="on"+a.type;this[b]&&this[b](a)},d.filterFindElements=function(a,b){a=d.makeArray(a);for(var e=[],f=0,g=a.length;g>f;f++){var h=a[f];if(d.isElement(h))if(b){c(h,b)&&e.push(h);for(var i=h.querySelectorAll(b),j=0,k=i.length;k>j;j++)e.push(i[j])}else e.push(h)}return e},d.debounceMethod=function(a,b,c){var d=a.prototype[b],e=b+"Timeout";a.prototype[b]=function(){var a=this[e];a&&clearTimeout(a);var b=arguments,f=this;this[e]=setTimeout(function(){d.apply(f,b),delete f[e]},c||100)}},d.toDashed=function(a){return a.replace(/(.)([A-Z])/g,function(a,b,c){return b+"-"+c}).toLowerCase()};var f=a.console;return d.htmlInit=function(c,e){b(function(){for(var b=d.toDashed(e),g=document.querySelectorAll(".js-"+b),h="data-"+b+"-options",i=0,j=g.length;j>i;i++){var k,l=g[i],m=l.getAttribute(h);try{k=m&&JSON.parse(m)}catch(n){f&&f.error("Error parsing "+h+" on "+l.nodeName.toLowerCase()+(l.id?"#"+l.id:"")+": "+n);continue}var o=new c(l,k),p=a.jQuery;p&&p.data(l,e,o)}})},d}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("outlayer/item",["eventEmitter/EventEmitter","get-size/get-size","get-style-property/get-style-property","fizzy-ui-utils/utils"],function(c,d,e,f){return b(a,c,d,e,f)}):"object"==typeof exports?module.exports=b(a,require("wolfy87-eventemitter"),require("get-size"),require("desandro-get-style-property"),require("fizzy-ui-utils")):(a.Outlayer={},a.Outlayer.Item=b(a,a.EventEmitter,a.getSize,a.getStyleProperty,a.fizzyUIUtils))}(window,function(a,b,c,d,e){"use strict";function f(a){for(var b in a)return!1;return b=null,!0}function g(a,b){a&&(this.element=a,this.layout=b,this.position={x:0,y:0},this._create())}function h(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}var i=a.getComputedStyle,j=i?function(a){return i(a,null)}:function(a){return a.currentStyle},k=d("transition"),l=d("transform"),m=k&&l,n=!!d("perspective"),o={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"otransitionend",transition:"transitionend"}[k],p=["transform","transition","transitionDuration","transitionProperty"],q=function(){for(var a={},b=0,c=p.length;c>b;b++){var e=p[b],f=d(e);f&&f!==e&&(a[e]=f)}return a}();e.extend(g.prototype,b.prototype),g.prototype._create=function(){this._transn={ingProperties:{},clean:{},onEnd:{}},this.css({position:"absolute"})},g.prototype.handleEvent=function(a){var b="on"+a.type;this[b]&&this[b](a)},g.prototype.getSize=function(){this.size=c(this.element)},g.prototype.css=function(a){var b=this.element.style;for(var c in a){var d=q[c]||c;b[d]=a[c]}},g.prototype.getPosition=function(){var a=j(this.element),b=this.layout.options,c=b.isOriginLeft,d=b.isOriginTop,e=a[c?"left":"right"],f=a[d?"top":"bottom"],g=this.layout.size,h=-1!=e.indexOf("%")?parseFloat(e)/100*g.width:parseInt(e,10),i=-1!=f.indexOf("%")?parseFloat(f)/100*g.height:parseInt(f,10);h=isNaN(h)?0:h,i=isNaN(i)?0:i,h-=c?g.paddingLeft:g.paddingRight,i-=d?g.paddingTop:g.paddingBottom,this.position.x=h,this.position.y=i},g.prototype.layoutPosition=function(){var a=this.layout.size,b=this.layout.options,c={},d=b.isOriginLeft?"paddingLeft":"paddingRight",e=b.isOriginLeft?"left":"right",f=b.isOriginLeft?"right":"left",g=this.position.x+a[d];c[e]=this.getXValue(g),c[f]="";var h=b.isOriginTop?"paddingTop":"paddingBottom",i=b.isOriginTop?"top":"bottom",j=b.isOriginTop?"bottom":"top",k=this.position.y+a[h];c[i]=this.getYValue(k),c[j]="",this.css(c),this.emitEvent("layout",[this])},g.prototype.getXValue=function(a){var b=this.layout.options;return b.percentPosition&&!b.isHorizontal?a/this.layout.size.width*100+"%":a+"px"},g.prototype.getYValue=function(a){var b=this.layout.options;return b.percentPosition&&b.isHorizontal?a/this.layout.size.height*100+"%":a+"px"},g.prototype._transitionTo=function(a,b){this.getPosition();var c=this.position.x,d=this.position.y,e=parseInt(a,10),f=parseInt(b,10),g=e===this.position.x&&f===this.position.y;if(this.setPosition(a,b),g&&!this.isTransitioning)return void this.layoutPosition();var h=a-c,i=b-d,j={};j.transform=this.getTranslate(h,i),this.transition({to:j,onTransitionEnd:{transform:this.layoutPosition},isCleaning:!0})},g.prototype.getTranslate=function(a,b){var c=this.layout.options;return a=c.isOriginLeft?a:-a,b=c.isOriginTop?b:-b,n?"translate3d("+a+"px, "+b+"px, 0)":"translate("+a+"px, "+b+"px)"},g.prototype.goTo=function(a,b){this.setPosition(a,b),this.layoutPosition()},g.prototype.moveTo=m?g.prototype._transitionTo:g.prototype.goTo,g.prototype.setPosition=function(a,b){this.position.x=parseInt(a,10),this.position.y=parseInt(b,10)},g.prototype._nonTransition=function(a){this.css(a.to),a.isCleaning&&this._removeStyles(a.to);for(var b in a.onTransitionEnd)a.onTransitionEnd[b].call(this)},g.prototype._transition=function(a){if(!parseFloat(this.layout.options.transitionDuration))return void this._nonTransition(a);var b=this._transn;for(var c in a.onTransitionEnd)b.onEnd[c]=a.onTransitionEnd[c];for(c in a.to)b.ingProperties[c]=!0,a.isCleaning&&(b.clean[c]=!0);if(a.from){this.css(a.from);var d=this.element.offsetHeight;d=null}this.enableTransition(a.to),this.css(a.to),this.isTransitioning=!0};var r="opacity,"+h(q.transform||"transform");g.prototype.enableTransition=function(){this.isTransitioning||(this.css({transitionProperty:r,transitionDuration:this.layout.options.transitionDuration}),this.element.addEventListener(o,this,!1))},g.prototype.transition=g.prototype[k?"_transition":"_nonTransition"],g.prototype.onwebkitTransitionEnd=function(a){this.ontransitionend(a)},g.prototype.onotransitionend=function(a){this.ontransitionend(a)};var s={"-webkit-transform":"transform","-moz-transform":"transform","-o-transform":"transform"};g.prototype.ontransitionend=function(a){if(a.target===this.element){var b=this._transn,c=s[a.propertyName]||a.propertyName;if(delete b.ingProperties[c],f(b.ingProperties)&&this.disableTransition(),c in b.clean&&(this.element.style[a.propertyName]="",delete b.clean[c]),c in b.onEnd){var d=b.onEnd[c];d.call(this),delete b.onEnd[c]}this.emitEvent("transitionEnd",[this])}},g.prototype.disableTransition=function(){this.removeTransitionStyles(),this.element.removeEventListener(o,this,!1),this.isTransitioning=!1},g.prototype._removeStyles=function(a){var b={};for(var c in a)b[c]="";this.css(b)};var t={transitionProperty:"",transitionDuration:""};return g.prototype.removeTransitionStyles=function(){this.css(t)},g.prototype.removeElem=function(){this.element.parentNode.removeChild(this.element),this.css({display:""}),this.emitEvent("remove",[this])},g.prototype.remove=function(){if(!k||!parseFloat(this.layout.options.transitionDuration))return void this.removeElem();var a=this;this.once("transitionEnd",function(){a.removeElem()}),this.hide()},g.prototype.reveal=function(){delete this.isHidden,this.css({display:""});var a=this.layout.options,b={},c=this.getHideRevealTransitionEndProperty("visibleStyle");b[c]=this.onRevealTransitionEnd,this.transition({from:a.hiddenStyle,to:a.visibleStyle,isCleaning:!0,onTransitionEnd:b})},g.prototype.onRevealTransitionEnd=function(){this.isHidden||this.emitEvent("reveal")},g.prototype.getHideRevealTransitionEndProperty=function(a){var b=this.layout.options[a];if(b.opacity)return"opacity";for(var c in b)return c},g.prototype.hide=function(){this.isHidden=!0,this.css({display:""});var a=this.layout.options,b={},c=this.getHideRevealTransitionEndProperty("hiddenStyle");b[c]=this.onHideTransitionEnd,this.transition({from:a.visibleStyle,to:a.hiddenStyle,isCleaning:!0,onTransitionEnd:b})},g.prototype.onHideTransitionEnd=function(){this.isHidden&&(this.css({display:"none"}),this.emitEvent("hide"))},g.prototype.destroy=function(){this.css({position:"",left:"",right:"",top:"",bottom:"",transition:"",transform:""})},g}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("outlayer/outlayer",["eventie/eventie","eventEmitter/EventEmitter","get-size/get-size","fizzy-ui-utils/utils","./item"],function(c,d,e,f,g){return b(a,c,d,e,f,g)}):"object"==typeof exports?module.exports=b(a,require("eventie"),require("wolfy87-eventemitter"),require("get-size"),require("fizzy-ui-utils"),require("./item")):a.Outlayer=b(a,a.eventie,a.EventEmitter,a.getSize,a.fizzyUIUtils,a.Outlayer.Item)}(window,function(a,b,c,d,e,f){"use strict";function g(a,b){var c=e.getQueryElement(a);if(!c)return void(h&&h.error("Bad element for "+this.constructor.namespace+": "+(c||a)));this.element=c,i&&(this.$element=i(this.element)),this.options=e.extend({},this.constructor.defaults),this.option(b);var d=++k;this.element.outlayerGUID=d,l[d]=this,this._create(),this.options.isInitLayout&&this.layout()}var h=a.console,i=a.jQuery,j=function(){},k=0,l={};return g.namespace="outlayer",g.Item=f,g.defaults={containerStyle:{position:"relative"},isInitLayout:!0,isOriginLeft:!0,isOriginTop:!0,isResizeBound:!0,isResizingContainer:!0,transitionDuration:"0.4s",hiddenStyle:{opacity:0,transform:"scale(0.001)"},visibleStyle:{opacity:1,transform:"scale(1)"}},e.extend(g.prototype,c.prototype),g.prototype.option=function(a){e.extend(this.options,a)},g.prototype._create=function(){this.reloadItems(),this.stamps=[],this.stamp(this.options.stamp),e.extend(this.element.style,this.options.containerStyle),this.options.isResizeBound&&this.bindResize()},g.prototype.reloadItems=function(){this.items=this._itemize(this.element.children)},g.prototype._itemize=function(a){for(var b=this._filterFindItemElements(a),c=this.constructor.Item,d=[],e=0,f=b.length;f>e;e++){var g=b[e],h=new c(g,this);d.push(h)}return d},g.prototype._filterFindItemElements=function(a){return e.filterFindElements(a,this.options.itemSelector)},g.prototype.getItemElements=function(){for(var a=[],b=0,c=this.items.length;c>b;b++)a.push(this.items[b].element);return a},g.prototype.layout=function(){this._resetLayout(),this._manageStamps();var a=void 0!==this.options.isLayoutInstant?this.options.isLayoutInstant:!this._isLayoutInited;this.layoutItems(this.items,a),this._isLayoutInited=!0},g.prototype._init=g.prototype.layout,g.prototype._resetLayout=function(){this.getSize()},g.prototype.getSize=function(){this.size=d(this.element)},g.prototype._getMeasurement=function(a,b){var c,f=this.options[a];f?("string"==typeof f?c=this.element.querySelector(f):e.isElement(f)&&(c=f),this[a]=c?d(c)[b]:f):this[a]=0},g.prototype.layoutItems=function(a,b){a=this._getItemsForLayout(a),this._layoutItems(a,b),this._postLayout()},g.prototype._getItemsForLayout=function(a){for(var b=[],c=0,d=a.length;d>c;c++){var e=a[c];e.isIgnored||b.push(e)}return b},g.prototype._layoutItems=function(a,b){if(this._emitCompleteOnItems("layout",a),a&&a.length){for(var c=[],d=0,e=a.length;e>d;d++){var f=a[d],g=this._getItemLayoutPosition(f);g.item=f,g.isInstant=b||f.isLayoutInstant,c.push(g)}this._processLayoutQueue(c)}},g.prototype._getItemLayoutPosition=function(){return{x:0,y:0}},g.prototype._processLayoutQueue=function(a){for(var b=0,c=a.length;c>b;b++){var d=a[b];this._positionItem(d.item,d.x,d.y,d.isInstant)}},g.prototype._positionItem=function(a,b,c,d){d?a.goTo(b,c):a.moveTo(b,c)},g.prototype._postLayout=function(){this.resizeContainer()},g.prototype.resizeContainer=function(){if(this.options.isResizingContainer){var a=this._getContainerSize();a&&(this._setContainerMeasure(a.width,!0),this._setContainerMeasure(a.height,!1))}},g.prototype._getContainerSize=j,g.prototype._setContainerMeasure=function(a,b){if(void 0!==a){var c=this.size;c.isBorderBox&&(a+=b?c.paddingLeft+c.paddingRight+c.borderLeftWidth+c.borderRightWidth:c.paddingBottom+c.paddingTop+c.borderTopWidth+c.borderBottomWidth),a=Math.max(a,0),this.element.style[b?"width":"height"]=a+"px"}},g.prototype._emitCompleteOnItems=function(a,b){function c(){e.dispatchEvent(a+"Complete",null,[b])}function d(){g++,g===f&&c()}var e=this,f=b.length;if(!b||!f)return void c();for(var g=0,h=0,i=b.length;i>h;h++){var j=b[h];j.once(a,d)}},g.prototype.dispatchEvent=function(a,b,c){var d=b?[b].concat(c):c;if(this.emitEvent(a,d),i)if(this.$element=this.$element||i(this.element),b){var e=i.Event(b);e.type=a,this.$element.trigger(e,c)}else this.$element.trigger(a,c)},g.prototype.ignore=function(a){var b=this.getItem(a);b&&(b.isIgnored=!0)},g.prototype.unignore=function(a){var b=this.getItem(a);b&&delete b.isIgnored},g.prototype.stamp=function(a){if(a=this._find(a)){this.stamps=this.stamps.concat(a);for(var b=0,c=a.length;c>b;b++){var d=a[b];this.ignore(d)}}},g.prototype.unstamp=function(a){if(a=this._find(a))for(var b=0,c=a.length;c>b;b++){var d=a[b];e.removeFrom(this.stamps,d),this.unignore(d)}},g.prototype._find=function(a){return a?("string"==typeof a&&(a=this.element.querySelectorAll(a)),a=e.makeArray(a)):void 0},g.prototype._manageStamps=function(){if(this.stamps&&this.stamps.length){this._getBoundingRect();for(var a=0,b=this.stamps.length;b>a;a++){var c=this.stamps[a];this._manageStamp(c)}}},g.prototype._getBoundingRect=function(){var a=this.element.getBoundingClientRect(),b=this.size;this._boundingRect={left:a.left+b.paddingLeft+b.borderLeftWidth,top:a.top+b.paddingTop+b.borderTopWidth,right:a.right-(b.paddingRight+b.borderRightWidth),bottom:a.bottom-(b.paddingBottom+b.borderBottomWidth)}},g.prototype._manageStamp=j,g.prototype._getElementOffset=function(a){var b=a.getBoundingClientRect(),c=this._boundingRect,e=d(a),f={left:b.left-c.left-e.marginLeft,top:b.top-c.top-e.marginTop,right:c.right-b.right-e.marginRight,bottom:c.bottom-b.bottom-e.marginBottom};return f},g.prototype.handleEvent=function(a){var b="on"+a.type;this[b]&&this[b](a)},g.prototype.bindResize=function(){this.isResizeBound||(b.bind(a,"resize",this),this.isResizeBound=!0)},g.prototype.unbindResize=function(){this.isResizeBound&&b.unbind(a,"resize",this),this.isResizeBound=!1},g.prototype.onresize=function(){function a(){b.resize(),delete b.resizeTimeout}this.resizeTimeout&&clearTimeout(this.resizeTimeout);var b=this;this.resizeTimeout=setTimeout(a,100)},g.prototype.resize=function(){this.isResizeBound&&this.needsResizeLayout()&&this.layout()},g.prototype.needsResizeLayout=function(){var a=d(this.element),b=this.size&&a;return b&&a.innerWidth!==this.size.innerWidth},g.prototype.addItems=function(a){var b=this._itemize(a);return b.length&&(this.items=this.items.concat(b)),b},g.prototype.appended=function(a){var b=this.addItems(a);b.length&&(this.layoutItems(b,!0),this.reveal(b))},g.prototype.prepended=function(a){var b=this._itemize(a);if(b.length){var c=this.items.slice(0);this.items=b.concat(c),this._resetLayout(),this._manageStamps(),this.layoutItems(b,!0),this.reveal(b),this.layoutItems(c)}},g.prototype.reveal=function(a){this._emitCompleteOnItems("reveal",a);for(var b=a&&a.length,c=0;b&&b>c;c++){var d=a[c];d.reveal()}},g.prototype.hide=function(a){this._emitCompleteOnItems("hide",a);for(var b=a&&a.length,c=0;b&&b>c;c++){var d=a[c];d.hide()}},g.prototype.revealItemElements=function(a){var b=this.getItems(a);this.reveal(b)},g.prototype.hideItemElements=function(a){var b=this.getItems(a);this.hide(b)},g.prototype.getItem=function(a){for(var b=0,c=this.items.length;c>b;b++){var d=this.items[b];if(d.element===a)return d}},g.prototype.getItems=function(a){a=e.makeArray(a);for(var b=[],c=0,d=a.length;d>c;c++){var f=a[c],g=this.getItem(f);g&&b.push(g)}return b},g.prototype.remove=function(a){var b=this.getItems(a);if(this._emitCompleteOnItems("remove",b),b&&b.length)for(var c=0,d=b.length;d>c;c++){var f=b[c];f.remove(),e.removeFrom(this.items,f)}},g.prototype.destroy=function(){var a=this.element.style;a.height="",a.position="",a.width="";for(var b=0,c=this.items.length;c>b;b++){var d=this.items[b];d.destroy()}this.unbindResize();var e=this.element.outlayerGUID;delete l[e],delete this.element.outlayerGUID,i&&i.removeData(this.element,this.constructor.namespace)},g.data=function(a){a=e.getQueryElement(a);var b=a&&a.outlayerGUID;return b&&l[b]},g.create=function(a,b){function c(){g.apply(this,arguments)}return Object.create?c.prototype=Object.create(g.prototype):e.extend(c.prototype,g.prototype),c.prototype.constructor=c,c.defaults=e.extend({},g.defaults),e.extend(c.defaults,b),c.prototype.settings={},c.namespace=a,c.data=g.data,c.Item=function(){f.apply(this,arguments)},c.Item.prototype=new f,e.htmlInit(c,a),i&&i.bridget&&i.bridget(a,c),c},g.Item=f,g}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("isotope/js/item",["outlayer/outlayer"],b):"object"==typeof exports?module.exports=b(require("outlayer")):(a.Isotope=a.Isotope||{},a.Isotope.Item=b(a.Outlayer))}(window,function(a){"use strict";function b(){a.Item.apply(this,arguments)}b.prototype=new a.Item,b.prototype._create=function(){this.id=this.layout.itemGUID++,a.Item.prototype._create.call(this),this.sortData={}},b.prototype.updateSortData=function(){if(!this.isIgnored){this.sortData.id=this.id,this.sortData["original-order"]=this.id,this.sortData.random=Math.random();var a=this.layout.options.getSortData,b=this.layout._sorters;for(var c in a){var d=b[c];this.sortData[c]=d(this.element,this)}}};var c=b.prototype.destroy;return b.prototype.destroy=function(){c.apply(this,arguments),this.css({display:""})},b}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("isotope/js/layout-mode",["get-size/get-size","outlayer/outlayer"],b):"object"==typeof exports?module.exports=b(require("get-size"),require("outlayer")):(a.Isotope=a.Isotope||{},a.Isotope.LayoutMode=b(a.getSize,a.Outlayer))}(window,function(a,b){"use strict";function c(a){this.isotope=a,a&&(this.options=a.options[this.namespace],this.element=a.element,this.items=a.filteredItems,this.size=a.size)}return function(){function a(a){return function(){return b.prototype[a].apply(this.isotope,arguments)}}for(var d=["_resetLayout","_getItemLayoutPosition","_manageStamp","_getContainerSize","_getElementOffset","needsResizeLayout"],e=0,f=d.length;f>e;e++){var g=d[e];c.prototype[g]=a(g)}}(),c.prototype.needsVerticalResizeLayout=function(){var b=a(this.isotope.element),c=this.isotope.size&&b;return c&&b.innerHeight!=this.isotope.size.innerHeight},c.prototype._getMeasurement=function(){this.isotope._getMeasurement.apply(this,arguments)},c.prototype.getColumnWidth=function(){this.getSegmentSize("column","Width")},c.prototype.getRowHeight=function(){this.getSegmentSize("row","Height")},c.prototype.getSegmentSize=function(a,b){var c=a+b,d="outer"+b;if(this._getMeasurement(c,d),!this[c]){var e=this.getFirstItemSize();this[c]=e&&e[d]||this.isotope.size["inner"+b]}},c.prototype.getFirstItemSize=function(){var b=this.isotope.filteredItems[0];return b&&b.element&&a(b.element)},c.prototype.layout=function(){this.isotope.layout.apply(this.isotope,arguments)},c.prototype.getSize=function(){this.isotope.getSize(),this.size=this.isotope.size},c.modes={},c.create=function(a,b){function d(){c.apply(this,arguments)}return d.prototype=new c,b&&(d.options=b),d.prototype.namespace=a,c.modes[a]=d,d},c}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("masonry/masonry",["outlayer/outlayer","get-size/get-size","fizzy-ui-utils/utils"],b):"object"==typeof exports?module.exports=b(require("outlayer"),require("get-size"),require("fizzy-ui-utils")):a.Masonry=b(a.Outlayer,a.getSize,a.fizzyUIUtils)}(window,function(a,b,c){var d=a.create("masonry");return d.prototype._resetLayout=function(){this.getSize(),this._getMeasurement("columnWidth","outerWidth"),this._getMeasurement("gutter","outerWidth"),this.measureColumns();var a=this.cols;for(this.colYs=[];a--;)this.colYs.push(0);this.maxY=0},d.prototype.measureColumns=function(){if(this.getContainerWidth(),!this.columnWidth){var a=this.items[0],c=a&&a.element;this.columnWidth=c&&b(c).outerWidth||this.containerWidth}var d=this.columnWidth+=this.gutter,e=this.containerWidth+this.gutter,f=e/d,g=d-e%d,h=g&&1>g?"round":"floor";f=Math[h](f),this.cols=Math.max(f,1)},d.prototype.getContainerWidth=function(){var a=this.options.isFitWidth?this.element.parentNode:this.element,c=b(a);this.containerWidth=c&&c.innerWidth},d.prototype._getItemLayoutPosition=function(a){a.getSize();var b=a.size.outerWidth%this.columnWidth,d=b&&1>b?"round":"ceil",e=Math[d](a.size.outerWidth/this.columnWidth);e=Math.min(e,this.cols);for(var f=this._getColGroup(e),g=Math.min.apply(Math,f),h=c.indexOf(f,g),i={x:this.columnWidth*h,y:g},j=g+a.size.outerHeight,k=this.cols+1-f.length,l=0;k>l;l++)this.colYs[h+l]=j;return i},d.prototype._getColGroup=function(a){if(2>a)return this.colYs;for(var b=[],c=this.cols+1-a,d=0;c>d;d++){var e=this.colYs.slice(d,d+a);b[d]=Math.max.apply(Math,e)}return b},d.prototype._manageStamp=function(a){var c=b(a),d=this._getElementOffset(a),e=this.options.isOriginLeft?d.left:d.right,f=e+c.outerWidth,g=Math.floor(e/this.columnWidth);g=Math.max(0,g);var h=Math.floor(f/this.columnWidth);h-=f%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var i=(this.options.isOriginTop?d.top:d.bottom)+c.outerHeight,j=g;h>=j;j++)this.colYs[j]=Math.max(i,this.colYs[j])},d.prototype._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var a={height:this.maxY};return this.options.isFitWidth&&(a.width=this._getContainerFitWidth()),a},d.prototype._getContainerFitWidth=function(){for(var a=0,b=this.cols;--b&&0===this.colYs[b];)a++;return(this.cols-a)*this.columnWidth-this.gutter},d.prototype.needsResizeLayout=function(){var a=this.containerWidth;return this.getContainerWidth(),a!==this.containerWidth},d}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("isotope/js/layout-modes/masonry",["../layout-mode","masonry/masonry"],b):"object"==typeof exports?module.exports=b(require("../layout-mode"),require("masonry-layout")):b(a.Isotope.LayoutMode,a.Masonry)}(window,function(a,b){"use strict";function c(a,b){for(var c in b)a[c]=b[c];return a}var d=a.create("masonry"),e=d.prototype._getElementOffset,f=d.prototype.layout,g=d.prototype._getMeasurement; c(d.prototype,b.prototype),d.prototype._getElementOffset=e,d.prototype.layout=f,d.prototype._getMeasurement=g;var h=d.prototype.measureColumns;d.prototype.measureColumns=function(){this.items=this.isotope.filteredItems,h.call(this)};var i=d.prototype._manageStamp;return d.prototype._manageStamp=function(){this.options.isOriginLeft=this.isotope.options.isOriginLeft,this.options.isOriginTop=this.isotope.options.isOriginTop,i.apply(this,arguments)},d}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("isotope/js/layout-modes/fit-rows",["../layout-mode"],b):"object"==typeof exports?module.exports=b(require("../layout-mode")):b(a.Isotope.LayoutMode)}(window,function(a){"use strict";var b=a.create("fitRows");return b.prototype._resetLayout=function(){this.x=0,this.y=0,this.maxY=0,this._getMeasurement("gutter","outerWidth")},b.prototype._getItemLayoutPosition=function(a){a.getSize();var b=a.size.outerWidth+this.gutter,c=this.isotope.size.innerWidth+this.gutter;0!==this.x&&b+this.x>c&&(this.x=0,this.y=this.maxY);var d={x:this.x,y:this.y};return this.maxY=Math.max(this.maxY,this.y+a.size.outerHeight),this.x+=b,d},b.prototype._getContainerSize=function(){return{height:this.maxY}},b}),function(a,b){"use strict";"function"==typeof define&&define.amd?define("isotope/js/layout-modes/vertical",["../layout-mode"],b):"object"==typeof exports?module.exports=b(require("../layout-mode")):b(a.Isotope.LayoutMode)}(window,function(a){"use strict";var b=a.create("vertical",{horizontalAlignment:0});return b.prototype._resetLayout=function(){this.y=0},b.prototype._getItemLayoutPosition=function(a){a.getSize();var b=(this.isotope.size.innerWidth-a.size.outerWidth)*this.options.horizontalAlignment,c=this.y;return this.y+=a.size.outerHeight,{x:b,y:c}},b.prototype._getContainerSize=function(){return{height:this.y}},b}),function(a,b){"use strict";"function"==typeof define&&define.amd?define(["outlayer/outlayer","get-size/get-size","matches-selector/matches-selector","fizzy-ui-utils/utils","isotope/js/item","isotope/js/layout-mode","isotope/js/layout-modes/masonry","isotope/js/layout-modes/fit-rows","isotope/js/layout-modes/vertical"],function(c,d,e,f,g,h){return b(a,c,d,e,f,g,h)}):"object"==typeof exports?module.exports=b(a,require("outlayer"),require("get-size"),require("desandro-matches-selector"),require("fizzy-ui-utils"),require("./item"),require("./layout-mode"),require("./layout-modes/masonry"),require("./layout-modes/fit-rows"),require("./layout-modes/vertical")):a.Isotope=b(a,a.Outlayer,a.getSize,a.matchesSelector,a.fizzyUIUtils,a.Isotope.Item,a.Isotope.LayoutMode)}(window,function(a,b,c,d,e,f,g){function h(a,b){return function(c,d){for(var e=0,f=a.length;f>e;e++){var g=a[e],h=c.sortData[g],i=d.sortData[g];if(h>i||i>h){var j=void 0!==b[g]?b[g]:b,k=j?1:-1;return(h>i?1:-1)*k}}return 0}}var i=a.jQuery,j=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^\s+|\s+$/g,"")},k=document.documentElement,l=k.textContent?function(a){return a.textContent}:function(a){return a.innerText},m=b.create("isotope",{layoutMode:"masonry",isJQueryFiltering:!0,sortAscending:!0});m.Item=f,m.LayoutMode=g,m.prototype._create=function(){this.itemGUID=0,this._sorters={},this._getSorters(),b.prototype._create.call(this),this.modes={},this.filteredItems=this.items,this.sortHistory=["original-order"];for(var a in g.modes)this._initLayoutMode(a)},m.prototype.reloadItems=function(){this.itemGUID=0,b.prototype.reloadItems.call(this)},m.prototype._itemize=function(){for(var a=b.prototype._itemize.apply(this,arguments),c=0,d=a.length;d>c;c++){var e=a[c];e.id=this.itemGUID++}return this._updateItemsSortData(a),a},m.prototype._initLayoutMode=function(a){var b=g.modes[a],c=this.options[a]||{};this.options[a]=b.options?e.extend(b.options,c):c,this.modes[a]=new b(this)},m.prototype.layout=function(){return!this._isLayoutInited&&this.options.isInitLayout?void this.arrange():void this._layout()},m.prototype._layout=function(){var a=this._getIsInstant();this._resetLayout(),this._manageStamps(),this.layoutItems(this.filteredItems,a),this._isLayoutInited=!0},m.prototype.arrange=function(a){function b(){d.reveal(c.needReveal),d.hide(c.needHide)}this.option(a),this._getIsInstant();var c=this._filter(this.items);this.filteredItems=c.matches;var d=this;this._bindArrangeComplete(),this._isInstant?this._noTransition(b):b(),this._sort(),this._layout()},m.prototype._init=m.prototype.arrange,m.prototype._getIsInstant=function(){var a=void 0!==this.options.isLayoutInstant?this.options.isLayoutInstant:!this._isLayoutInited;return this._isInstant=a,a},m.prototype._bindArrangeComplete=function(){function a(){b&&c&&d&&e.dispatchEvent("arrangeComplete",null,[e.filteredItems])}var b,c,d,e=this;this.once("layoutComplete",function(){b=!0,a()}),this.once("hideComplete",function(){c=!0,a()}),this.once("revealComplete",function(){d=!0,a()})},m.prototype._filter=function(a){var b=this.options.filter;b=b||"*";for(var c=[],d=[],e=[],f=this._getFilterTest(b),g=0,h=a.length;h>g;g++){var i=a[g];if(!i.isIgnored){var j=f(i);j&&c.push(i),j&&i.isHidden?d.push(i):j||i.isHidden||e.push(i)}}return{matches:c,needReveal:d,needHide:e}},m.prototype._getFilterTest=function(a){return i&&this.options.isJQueryFiltering?function(b){return i(b.element).is(a)}:"function"==typeof a?function(b){return a(b.element)}:function(b){return d(b.element,a)}},m.prototype.updateSortData=function(a){var b;a?(a=e.makeArray(a),b=this.getItems(a)):b=this.items,this._getSorters(),this._updateItemsSortData(b)},m.prototype._getSorters=function(){var a=this.options.getSortData;for(var b in a){var c=a[b];this._sorters[b]=n(c)}},m.prototype._updateItemsSortData=function(a){for(var b=a&&a.length,c=0;b&&b>c;c++){var d=a[c];d.updateSortData()}};var n=function(){function a(a){if("string"!=typeof a)return a;var c=j(a).split(" "),d=c[0],e=d.match(/^\[(.+)\]$/),f=e&&e[1],g=b(f,d),h=m.sortDataParsers[c[1]];return a=h?function(a){return a&&h(g(a))}:function(a){return a&&g(a)}}function b(a,b){var c;return c=a?function(b){return b.getAttribute(a)}:function(a){var c=a.querySelector(b);return c&&l(c)}}return a}();m.sortDataParsers={parseInt:function(a){return parseInt(a,10)},parseFloat:function(a){return parseFloat(a)}},m.prototype._sort=function(){var a=this.options.sortBy;if(a){var b=[].concat.apply(a,this.sortHistory),c=h(b,this.options.sortAscending);this.filteredItems.sort(c),a!=this.sortHistory[0]&&this.sortHistory.unshift(a)}},m.prototype._mode=function(){var a=this.options.layoutMode,b=this.modes[a];if(!b)throw new Error("No layout mode: "+a);return b.options=this.options[a],b},m.prototype._resetLayout=function(){b.prototype._resetLayout.call(this),this._mode()._resetLayout()},m.prototype._getItemLayoutPosition=function(a){return this._mode()._getItemLayoutPosition(a)},m.prototype._manageStamp=function(a){this._mode()._manageStamp(a)},m.prototype._getContainerSize=function(){return this._mode()._getContainerSize()},m.prototype.needsResizeLayout=function(){return this._mode().needsResizeLayout()},m.prototype.appended=function(a){var b=this.addItems(a);if(b.length){var c=this._filterRevealAdded(b);this.filteredItems=this.filteredItems.concat(c)}},m.prototype.prepended=function(a){var b=this._itemize(a);if(b.length){this._resetLayout(),this._manageStamps();var c=this._filterRevealAdded(b);this.layoutItems(this.filteredItems),this.filteredItems=c.concat(this.filteredItems),this.items=b.concat(this.items)}},m.prototype._filterRevealAdded=function(a){var b=this._filter(a);return this.hide(b.needHide),this.reveal(b.matches),this.layoutItems(b.matches,!0),b.matches},m.prototype.insert=function(a){var b=this.addItems(a);if(b.length){var c,d,e=b.length;for(c=0;e>c;c++)d=b[c],this.element.appendChild(d.element);var f=this._filter(b).matches;for(c=0;e>c;c++)b[c].isLayoutInstant=!0;for(this.arrange(),c=0;e>c;c++)delete b[c].isLayoutInstant;this.reveal(f)}};var o=m.prototype.remove;return m.prototype.remove=function(a){a=e.makeArray(a);var b=this.getItems(a);o.call(this,a);var c=b&&b.length;if(c)for(var d=0;c>d;d++){var f=b[d];e.removeFrom(this.filteredItems,f)}},m.prototype.shuffle=function(){for(var a=0,b=this.items.length;b>a;a++){var c=this.items[a];c.sortData.random=Math.random()}this.options.sortBy="random",this._sort(),this._layout()},m.prototype._noTransition=function(a){var b=this.options.transitionDuration;this.options.transitionDuration=0;var c=a.call(this);return this.options.transitionDuration=b,c},m.prototype.getFilteredItemElements=function(){for(var a=[],b=0,c=this.filteredItems.length;c>b;b++)a.push(this.filteredItems[b].element);return a},m}); /*! * jquery.counterup.js 1.0 * * Copyright 2013, Benjamin Intal http://gambit.ph @bfintal * Released under the GPL v2 License * * Date: Nov 26, 2013 */(function(e){"use strict";e.fn.counterUp=function(t){var n=e.extend({time:400,delay:10},t);return this.each(function(){var t=e(this),r=n,i=function(){var e=[],n=r.time/r.delay,i=t.text(),s=/[0-9]+,[0-9]+/.test(i);i=i.replace(/,/g,"");var o=/^[0-9]+$/.test(i),u=/^[0-9]+\.[0-9]+$/.test(i),a=u?(i.split(".")[1]||[]).length:0;for(var f=n;f>=1;f--){var l=parseInt(i/n*f);u&&(l=parseFloat(i/n*f).toFixed(a));if(s)while(/(\d+)(\d{3})/.test(l.toString()))l=l.toString().replace(/(\d+)(\d{3})/,"$1,$2");e.unshift(l)}t.data("counterup-nums",e);t.text("0");var c=function(){t.text(t.data("counterup-nums").shift());if(t.data("counterup-nums").length)setTimeout(t.data("counterup-func"),r.delay);else{delete t.data("counterup-nums");t.data("counterup-nums",null);t.data("counterup-func",null)}};t.data("counterup-func",c);setTimeout(t.data("counterup-func"),r.delay)};t.waypoint(i,{offset:"100%",triggerOnce:!0})})}})(jQuery); /* * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ * * Uses the built in easing capabilities added In jQuery 1.1 * to offer multiple easing options * * TERMS OF USE - EASING EQUATIONS * * Open source under the BSD License. * * Copyright © 2001 Robert Penner * All rights reserved. * * TERMS OF USE - jQuery Easing * * Open source under the BSD License. * * Copyright © 2008 George McGinley Smith * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ jQuery.easing.jswing=jQuery.easing.swing;jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,f,a,h,g){return jQuery.easing[jQuery.easing.def](e,f,a,h,g)},easeInQuad:function(e,f,a,h,g){return h*(f/=g)*f+a},easeOutQuad:function(e,f,a,h,g){return -h*(f/=g)*(f-2)+a},easeInOutQuad:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f+a}return -h/2*((--f)*(f-2)-1)+a},easeInCubic:function(e,f,a,h,g){return h*(f/=g)*f*f+a},easeOutCubic:function(e,f,a,h,g){return h*((f=f/g-1)*f*f+1)+a},easeInOutCubic:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f+a}return h/2*((f-=2)*f*f+2)+a},easeInQuart:function(e,f,a,h,g){return h*(f/=g)*f*f*f+a},easeOutQuart:function(e,f,a,h,g){return -h*((f=f/g-1)*f*f*f-1)+a},easeInOutQuart:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+a}return -h/2*((f-=2)*f*f*f-2)+a},easeInQuint:function(e,f,a,h,g){return h*(f/=g)*f*f*f*f+a},easeOutQuint:function(e,f,a,h,g){return h*((f=f/g-1)*f*f*f*f+1)+a},easeInOutQuint:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+a}return h/2*((f-=2)*f*f*f*f+2)+a},easeInSine:function(e,f,a,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+a},easeOutSine:function(e,f,a,h,g){return h*Math.sin(f/g*(Math.PI/2))+a},easeInOutSine:function(e,f,a,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+a},easeInExpo:function(e,f,a,h,g){return(f==0)?a:h*Math.pow(2,10*(f/g-1))+a},easeOutExpo:function(e,f,a,h,g){return(f==g)?a+h:h*(-Math.pow(2,-10*f/g)+1)+a},easeInOutExpo:function(e,f,a,h,g){if(f==0){return a}if(f==g){return a+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+a}return h/2*(-Math.pow(2,-10*--f)+2)+a},easeInCirc:function(e,f,a,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+a},easeOutCirc:function(e,f,a,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+a},easeInOutCirc:function(e,f,a,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+a}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+a},easeInElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(go)return clearInterval(d),void(n&&"function"==typeof n&&n());var a=1e3,f=60*a,u=60*f,l=24*u,c=Math.floor(o/l),h=Math.floor(o%l/u),x=Math.floor(o%u/f),g=Math.floor(o%f/a),y=1===c?r.day:r.days,m=1===h?r.hour:r.hours,v=1===x?r.minute:r.minutes,D=1===g?r.second:r.seconds;c=String(c).length>=2?c:"0"+c,h=String(h).length>=2?h:"0"+h,x=String(x).length>=2?x:"0"+x,g=String(g).length>=2?g:"0"+g,i.find(".days").text(c),i.find(".hours").text(h),i.find(".minutes").text(x),i.find(".seconds").text(g),i.find(".days_text").text(y),i.find(".hours_text").text(m),i.find(".minutes_text").text(v),i.find(".seconds_text").text(D)}var r=t.extend({date:null,offset:null,day:"Day",days:"Days",hour:"Hour",hours:"Hours",minute:"Minute",minutes:"Mins",second:"Sec",seconds:"Secs"},e);r.date||t.error("Date is not defined."),Date.parse(r.date)||t.error("Incorrect date format, it should look like this, 12/24/2012 12:00:00.");var i=this,s=function(){var t=new Date,e=t.getTime()+6e4*t.getTimezoneOffset(),n=new Date(e+36e5*r.offset);return n},d=setInterval(o,1e3)}}(jQuery); ================================================ FILE: docs/assets/js/validator.js ================================================ /* ======================================================================== * Bootstrap (plugin): validator.js v0.10.2 * ======================================================================== * The MIT License (MIT) * * Copyright (c) 2015 Cina Saffary. * Made by @1000hz in the style of Bootstrap 3 era @fat * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * ======================================================================== */ +function ($) { 'use strict'; // VALIDATOR CLASS DEFINITION // ========================== function getValue($el) { return $el.is('[type="checkbox"]') ? $el.prop('checked') : $el.is('[type="radio"]') ? !!$('[name="' + $el.attr('name') + '"]:checked').length : $.trim($el.val()) } var Validator = function (element, options) { this.options = options this.$element = $(element) this.$inputs = this.$element.find(Validator.INPUT_SELECTOR) this.$btn = $('button[type="submit"], input[type="submit"]') .filter('[form="' + this.$element.attr('id') + '"]') .add(this.$element.find('input[type="submit"], button[type="submit"]')) options.errors = $.extend({}, Validator.DEFAULTS.errors, options.errors) for (var custom in options.custom) { if (!options.errors[custom]) throw new Error('Missing default error message for custom validator: ' + custom) } $.extend(Validator.VALIDATORS, options.custom) this.$element.attr('novalidate', true) // disable automatic native validation this.toggleSubmit() this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', Validator.INPUT_SELECTOR, $.proxy(this.onInput, this)) this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this)) this.$element.find('[data-match]').each(function () { var $this = $(this) var target = $this.data('match') $(target).on('input.bs.validator', function (e) { getValue($this) && $this.trigger('input.bs.validator') }) }) } Validator.INPUT_SELECTOR = ':input:not([type="submit"], button):enabled:visible' Validator.FOCUS_OFFSET = 20 Validator.DEFAULTS = { delay: 500, html: false, disable: true, focus: true, custom: {}, errors: { match: 'Does not match', minlength: 'Not long enough' }, feedback: { success: 'glyphicon-ok', error: 'glyphicon-remove' } } Validator.VALIDATORS = { 'native': function ($el) { var el = $el[0] return el.checkValidity ? el.checkValidity() : true }, 'match': function ($el) { var target = $el.data('match') return !$el.val() || $el.val() === $(target).val() }, 'minlength': function ($el) { var minlength = $el.data('minlength') return !$el.val() || $el.val().length >= minlength } } Validator.prototype.onInput = function (e) { var self = this var $el = $(e.target) var deferErrors = e.type !== 'focusout' this.validateInput($el, deferErrors).done(function () { self.toggleSubmit() }) } Validator.prototype.validateInput = function ($el, deferErrors) { var value = getValue($el) var prevValue = $el.data('bs.validator.previous') var prevErrors = $el.data('bs.validator.errors') var errors if (prevValue === value) return $.Deferred().resolve() else $el.data('bs.validator.previous', value) if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]') var e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}) this.$element.trigger(e) if (e.isDefaultPrevented()) return var self = this return this.runValidators($el).done(function (errors) { $el.data('bs.validator.errors', errors) errors.length ? deferErrors ? self.defer($el, self.showErrors) : self.showErrors($el) : self.clearErrors($el) if (!prevErrors || errors.toString() !== prevErrors.toString()) { e = errors.length ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors}) : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors}) self.$element.trigger(e) } self.toggleSubmit() self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]})) }) } Validator.prototype.runValidators = function ($el) { var errors = [] var deferred = $.Deferred() var options = this.options $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject() $el.data('bs.validator.deferred', deferred) function getErrorMessage(key) { return $el.data(key + '-error') || $el.data('error') || key == 'native' && $el[0].validationMessage || options.errors[key] } $.each(Validator.VALIDATORS, $.proxy(function (key, validator) { if ((getValue($el) || $el.attr('required')) && ($el.data(key) || key == 'native') && !validator.call(this, $el)) { var error = getErrorMessage(key) !~errors.indexOf(error) && errors.push(error) } }, this)) if (!errors.length && getValue($el) && $el.data('remote')) { this.defer($el, function () { var data = {} data[$el.attr('name')] = getValue($el) $.get($el.data('remote'), data) .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) }) .always(function () { deferred.resolve(errors)}) }) } else deferred.resolve(errors) return deferred.promise() } Validator.prototype.validate = function () { var self = this $.when(this.$inputs.map(function (el) { return self.validateInput($(this), false) })).then(function () { self.toggleSubmit() self.focusError() }) return this } Validator.prototype.focusError = function () { if (!this.options.focus) return var $input = $(".has-error:first :input") if ($input.length === 0) return $(document.body).animate({scrollTop: $input.offset().top - Validator.FOCUS_OFFSET}, 250) $input.focus() } Validator.prototype.showErrors = function ($el) { var method = this.options.html ? 'html' : 'text' var errors = $el.data('bs.validator.errors') var $group = $el.closest('.form-group') var $block = $group.find('.help-block.with-errors') var $feedback = $group.find('.form-control-feedback') if (!errors.length) return errors = $('
    ') .addClass('list-unstyled') .append($.map(errors, function (error) { return $('
  • ')[method](error) })) $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html()) $block.empty().append(errors) $group.addClass('has-error has-danger') $group.hasClass('has-feedback') && $feedback.removeClass(this.options.feedback.success) && $feedback.addClass(this.options.feedback.error) && $group.removeClass('has-success') } Validator.prototype.clearErrors = function ($el) { var $group = $el.closest('.form-group') var $block = $group.find('.help-block.with-errors') var $feedback = $group.find('.form-control-feedback') $block.html($block.data('bs.validator.originalContent')) $group.removeClass('has-error has-danger') $group.hasClass('has-feedback') && $feedback.removeClass(this.options.feedback.error) && getValue($el) && $feedback.addClass(this.options.feedback.success) && $group.addClass('has-success') } Validator.prototype.hasErrors = function () { function fieldErrors() { return !!($(this).data('bs.validator.errors') || []).length } return !!this.$inputs.filter(fieldErrors).length } Validator.prototype.isIncomplete = function () { function fieldIncomplete() { return !getValue($(this)) } return !!this.$inputs.filter('[required]').filter(fieldIncomplete).length } Validator.prototype.onSubmit = function (e) { this.validate() if (this.isIncomplete() || this.hasErrors()) e.preventDefault() } Validator.prototype.toggleSubmit = function () { if (!this.options.disable) return this.$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors()) } Validator.prototype.defer = function ($el, callback) { callback = $.proxy(callback, this, $el) if (!this.options.delay) return callback() window.clearTimeout($el.data('bs.validator.timeout')) $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay)) } Validator.prototype.destroy = function () { this.$element .removeAttr('novalidate') .removeData('bs.validator') .off('.bs.validator') .find('.form-control-feedback') .removeClass([this.options.feedback.error, this.options.feedback.success].join(' ')) this.$inputs .off('.bs.validator') .removeData(['bs.validator.errors', 'bs.validator.deferred', 'bs.validator.previous']) .each(function () { var $this = $(this) var timeout = $this.data('bs.validator.timeout') window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout') }) this.$element.find('.help-block.with-errors').each(function () { var $this = $(this) var originalContent = $this.data('bs.validator.originalContent') $this .removeData('bs.validator.originalContent') .html(originalContent) }) this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled') this.$element.find('.has-error, .has-danger').removeClass('has-error has-danger') return this } // VALIDATOR PLUGIN DEFINITION // =========================== function Plugin(option) { return this.each(function () { var $this = $(this) var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option) var data = $this.data('bs.validator') if (!data && option == 'destroy') return if (!data) $this.data('bs.validator', (data = new Validator(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.validator $.fn.validator = Plugin $.fn.validator.Constructor = Validator // VALIDATOR NO CONFLICT // ===================== $.fn.validator.noConflict = function () { $.fn.validator = old return this } // VALIDATOR DATA-API // ================== $(window).on('load', function () { $('form[data-toggle="validator"]').each(function () { var $form = $(this) Plugin.call($form, $form.data()) }) }) }(jQuery); ================================================ FILE: docs/conversion.md ================================================ --- layout: default title: Conversion permalink: /conversion/ redirect_from: - /docs/conversion.md/ - /docs/conversion/ --- # Conversion Matrix * TOC {:toc} This document outlines all possible conversion details regarding `compose.yaml` values to Kubernetes / OpenShift artifacts. ## Version Support Under the hood, we're using [compose-go](https://github.com/compose-spec/compose-go), the reference library for parsing Compose files. We should be able to load all versions of Compose files. We're doing our best to keep it up to date as soon as possible in our releases to be compatible with the latest features defined in the [Compose specification](https://github.com/compose-spec/compose-spec/blob/master/spec.md). If you absolutely need a feature we don't support yet, please open a PR! ## Conversion Table **Glossary:** - **✓:** Converts - **-:** Not in this Compose Version - **n:** Not yet implemented - **x:** Not applicable / no 1-1 conversion | Keys | V1 | V2 | V3 | Kubernetes / OpenShift | Notes | | ---------------------- | -- | -- | -- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | build | ✓ | ✓ | ✓ | | Builds/Pushes to Docker repository. See [user guide on build and push image](https://kompose.io/user-guide/#build-and-push-image) | | build: context | ✓ | ✓ | ✓ | | | | build: dockerfile | ✓ | ✓ | ✓ | | | | build: args | n | n | n | | | | build: cache_from | - | - | n | | | | cap_add | ✓ | ✓ | ✓ | Container.SecurityContext.Capabilities.Add | | | cap_drop | ✓ | ✓ | ✓ | Container.SecurityContext.Capabilities.Drop | | | command | ✓ | ✓ | ✓ | Container.Args | | | configs | n | n | ✓ | | | | configs: short-syntax | n | n | ✓ | | Only create configMap | | configs: long-syntax | n | n | ✓ | | If target path is /, ignore this and only create configMap | | cgroup_parent | x | x | x | | Not supported within Kubernetes. See issue https://github.com/kubernetes/kubernetes/issues/11986 | | container_name | ✓ | ✓ | ✓ | Metadata.Name + Deployment.Spec.Containers.Name | | | credential_spec | x | x | x | | Only applicable to Windows containers | | deploy | - | - | ✓ | | | | deploy: mode | - | - | ✓ | | | | deploy: replicas | - | - | ✓ | Deployment.Spec.Replicas / DeploymentConfig.Spec.Replicas | | | deploy: placement | - | - | ✓ | Affinity | | | deploy: update_config | - | - | ✓ | Workload.Spec.Strategy | Deployment / DeploymentConfig | | deploy: resources | - | - | ✓ | Containers.Resources.Limits.Memory / Containers.Resources.Limits.CPU | Support for memory as well as cpu | | deploy: restart_policy | - | - | ✓ | Pod generation | This generated a Pod, see the [user guide on restart](http://kompose.io/user-guide/#restart) | | deploy: labels | - | - | ✓ | Workload.Metadata.Labels | Only applied to workload resource | | devices | x | x | x | | Not supported within Kubernetes, See issue https://github.com/kubernetes/kubernetes/issues/5607 | | depends_on | x | x | x | | | | dns | x | x | x | | Not used within Kubernetes. Kubernetes uses a managed DNS server | | dns_search | x | x | x | | See `dns` key | | domainname | ✓ | ✓ | ✓ | SubDomain | | | tmpfs | ✓ | ✓ | ✓ | Containers.Volumes.EmptyDir | Creates emptyDirvolume with medium set to Memory & mounts given directory inside container | | entrypoint | ✓ | ✓ | ✓ | Container.Command | | | env_file | n | n | ✓ | | | | environment | ✓ | ✓ | ✓ | Container.Env | | | expose | ✓ | ✓ | ✓ | Service.Spec.Ports | | | endpoint_mode | n | n | ✓ | | If endpoint_mode=vip, the created Service will be forced to set to NodePort type | | extends | ✓ | ✓ | ✓ | | Extends by utilizing the same image supplied | | external_links | x | x | x | | Kubernetes uses a flat-structure for all containers and thus external_links does not have a 1-1 conversion | | extra_hosts | n | n | n | | | | group_add | ✓ | ✓ | ✓ | | | | healthcheck | - | n | ✓ | | | | hostname | ✓ | ✓ | ✓ | HostName | | | image | ✓ | ✓ | ✓ | Deployment.Spec.Containers.Image | | | isolation | x | x | x | | Not applicable as this applies to Windows with HyperV support | | labels | ✓ | ✓ | ✓ | Metadata.Annotations | | | links | x | x | x | | All containers in the same pod are accessible in Kubernetes | | logging | x | x | x | | Kubernetes has built-in logging support at the node-level | | network_mode | x | x | x | | Kubernetes uses its own cluster networking | | networks | ✓ | ✓ | ✓ | | See `networks` key | | networks: aliases | x | x | x | | See `networks` key | | networks: addresses | x | x | x | | See `networks` key | | pid | ✓ | ✓ | ✓ | HostPID | | | ports | ✓ | ✓ | ✓ | Service.Spec.Ports | | | ports: short-syntax | ✓ | ✓ | ✓ | Service.Spec.Ports | | | ports: long-syntax | - | - | ✓ | Service.Spec.Ports | | | secrets | - | - | ✓ | Secret | External Secret is not Supported | | secrets: short-syntax | - | - | ✓ | Secret | External Secret is not Supported | | secrets: long-syntax | - | - | ✓ | Secret | External Secret is not Supported | | security_opt | x | x | x | | Kubernetes uses its own container naming scheme | | stop_grace_period | ✓ | ✓ | ✓ | TerminationGracePeriodSeconds | | | stop_signal | x | x | x | | Not supported within Kubernetes. See issue https://github.com/kubernetes/kubernetes/issues/30051 | | sysctls | n | n | n | | | | ulimits | x | x | x | | Not supported within Kubernetes. See issue https://github.com/kubernetes/kubernetes/issues/3595 | | userns_mode | x | x | x | | Not supported within Kubernetes and ignored in Compose Version 3 | | volumes | ✓ | ✓ | ✓ | PersistentVolumeClaim | Creates a PersistentVolumeClaim. Can only be created if there is already a PersistentVolume within the cluster | | volumes: short-syntax | ✓ | ✓ | ✓ | PersistentVolumeClaim | Creates a PersistentVolumeClaim. Can only be created if there is already a PersistentVolume within the cluster | | volumes: long-syntax | - | - | ✓ | PersistentVolumeClaim | Creates a PersistentVolumeClaim. Can only be created if there is already a PersistentVolume within the cluster | | restart | ✓ | ✓ | ✓ | | | | | | | | | | | **Volume** | x | x | x | | | | driver | x | x | x | | | | driver_opts | x | x | x | | | | external | x | x | x | | | | labels | x | x | x | | | | | | | | | | | **Network** | x | x | x | | | | driver | x | x | x | | | | driver_opts | x | x | x | | | | enable_ipv6 | x | x | x | | | | ipam | x | x | x | | | | internal | x | x | x | | | | labels | x | x | x | | | | external | x | x | x | | | | | | | | | | | **Configs** | x | x | x | | | | environment | x | ✓ | ✓ | | | | file | ✓ | ✓ | ✓ | | | | content | x | ✓ | ✓ | | | | labels | x | x | x | | | | external | x | x | x | | | ================================================ FILE: docs/development.md ================================================ --- layout: default permalink: /development/ title: Development redirect_from: - /docs/development.md/ - /docs/development/ --- # Development Guide ## Building Kompose Read about building kompose [here](https://github.com/kubernetes/kompose#development-and-building-of-kompose). ## Workflow ### Fork the main repository 1. Go to https://github.com/kubernetes/kompose 2. Click the "Fork" button (at the top right) ### Clone your fork The commands below require that you have `$GOPATH`. We highly recommended you put the Kompose code into your `$GOPATH`. ```console git clone https://github.com/$YOUR_GITHUB_USERNAME/kompose.git $GOPATH/src/github.com/kubernetes/kompose cd $GOPATH/src/github.com/kubernetes/kompose git remote add upstream 'https://github.com/kubernetes/kompose' ``` ### Create a branch and make changes ```console git checkout -b myfeature # Make your code changes ``` ### Keeping your development fork in sync ```console git fetch upstream git rebase upstream/main ``` Note: If you have write access to the main repository at github.com/kubernetes/kompose, you should modify your git configuration so that you can't accidentally push to upstream: ```console git remote set-url --push upstream no_push ``` ### Committing changes to your fork ```console git commit git push -f origin myfeature ``` ### Creating a pull request 1. Visit https://github.com/$YOUR_GITHUB_USERNAME/kompose.git 2. Click the "Compare and pull request" button next to your "myfeature" branch. 3. Check out the pull request process for more details ## Go Modules and dependency management Kompose uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies. If you want to introduce changes to dependencies, please ensure that `go.mod` and `go.sum` are updated properly. ##### Updating Kubernetes and OpenShift Kubernetes version depends on what version is OpenShift using. OpenShift is using forked Kubernetes to carry some patches. Currently, it is not possible to use a different Kubernetes version from the version that OpenShift uses. (for more see comments in `go.mod`) ### Adding CLI tests [Kompose CLI tests](https://github.com/kubernetes/kompose/tree/main/script/test/cmd) run `kompose convert` with compose files, and cross-check the k8s and OpenShift artifacts generated with the template files. ### CI For Kompose, we use numerous CI's: - [TravisCI](https://travis-ci.org/kubernetes/kompose): Unit and CLI tests - [SemaphoreCI](https://semaphoreci.com/cdrage/kompose-2): Integration / cluster tests - [Fabric8CI](http://jenkins.cd.k8s.fabric8.io/): Secondary integration tests / future cluster tests ================================================ FILE: docs/feed.xml ================================================ --- layout: null --- {{ site.title | xml_escape }} {{ site.description | xml_escape }} {{ site.url }}{{ site.baseurl }}/ {{ site.time | date_to_rfc822 }} {{ site.time | date_to_rfc822 }} Jekyll v{{ jekyll.version }} {% for doc in site.docs limit:10 %} {{ doc.title | xml_escape }} {{ doc.content | xml_escape }} {{ doc.date | date_to_rfc822 }} {{ doc.url | prepend: site.baseurl | prepend: site.url }} {{ doc.url | prepend: site.baseurl | prepend: site.url }} {% endfor %} ================================================ FILE: docs/getting-started.md ================================================ --- layout: default permalink: /getting-started/ title: Getting Started redirect_from: - /docs/getting-started.md/ - /docs/getting-started/ --- # Getting Started * TOC {:toc} This is how you'll get started with Kompose! There are three different guides depending on your container orchestrator as well as operating system. For beginners and the most compatibility, follow the _Minikube and Kompose_ guide. ## Minikube and Kompose In this guide, we'll deploy a sample `compose.yaml` file to a Kubernetes cluster. Requirements: - [minikube](https://github.com/kubernetes/minikube) - [kompose](https://github.com/kubernetes/kompose) **Start `minikube`:** If you don't already have a Kubernetes cluster running, [minikube](https://github.com/kubernetes/minikube) is the best way to get started. ```sh $ minikube start Starting local Kubernetes v1.7.5 cluster... Starting VM... Getting VM IP address... Moving files into cluster... Setting up certs... Connecting to cluster... Setting up kubeconfig... Starting cluster components... Kubectl is now configured to use the cluster ``` **Download an [example Compose file](https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml), or use your own:** ```sh wget https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml ``` **Convert your Compose file to Kubernetes:** Run `kompose convert` in the same directory as your `compose.yaml` file. ```sh $ kompose convert INFO Kubernetes file "frontend-service.yaml" created INFO Kubernetes file "redis-leader-service.yaml" created INFO Kubernetes file "redis-replica-service.yaml" created INFO Kubernetes file "frontend-deployment.yaml" created INFO Kubernetes file "redis-leader-deployment.yaml" created INFO Kubernetes file "redis-replica-deployment.yaml" created ``` Then you can use `kubectl apply` to create these resources in Kubernetes. **Access the newly deployed service:** Now that your service has been deployed, let's access it. If you're using `minikube` you may access it via the `minikube service` command. ```sh $ minikube service frontend ``` Otherwise, use `kubectl` to see what IP the service is using: ```sh $ kubectl describe svc frontend Name: frontend Namespace: default Labels: service=frontend Selector: service=frontend Type: LoadBalancer IP: 10.0.0.183 LoadBalancer Ingress: 123.45.67.89 Port: 80 80/TCP NodePort: 80 31144/TCP Endpoints: 172.17.0.4:80 Session Affinity: None No events. ``` Note: If you're using a cloud provider, your IP will be listed next to `LoadBalancer Ingress`. If you have yet to expose your service (for example, within GCE), use the command: ```sh kubectl expose deployment frontend --type="LoadBalancer" ``` To check functionality, you may also `curl` the URL. ```sh $ curl http://123.45.67.89 ``` ## Minishift and Kompose In this guide, we'll deploy a sample `compose.yaml` file to an OpenShift cluster. Requirements: - [minishift](https://github.com/minishift/minishift) - [kompose](https://github.com/kubernetes/kompose) - An OpenShift route created **Note:** The service will NOT be accessible until you create an OpenShift route with `oc expose`. You must also have a virtualization environment setup. By default, `minishift` uses KVM. **Start `minishift`:** [Minishift](https://github.com/minishift/minishift) is a tool that helps run OpenShift locally using a single-node cluster inside a VM. Similar to [minikube](https://github.com/kubernetes/minikube). ```sh $ minishift start Starting local OpenShift cluster using 'kvm' hypervisor... -- Checking OpenShift client ... OK -- Checking Docker client ... OK -- Checking Docker version ... OK -- Checking for existing OpenShift container ... OK ... ``` **Download an [example Compose file](https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml), or use your own:** ```sh wget https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml ``` **Convert your Compose file to OpenShift:** Run `kompose convert --provider=openshift` in the same directory as your `compose.yaml` file. ```sh $ kompose convert --provider=openshift INFO OpenShift file "frontend-service.yaml" created INFO OpenShift file "redis-leader-service.yaml" created INFO OpenShift file "redis-replica-service.yaml" created INFO OpenShift file "frontend-deploymentconfig.yaml" created INFO OpenShift file "frontend-imagestream.yaml" created INFO OpenShift file "redis-leader-deploymentconfig.yaml" created INFO OpenShift file "redis-leader-imagestream.yaml" created INFO OpenShift file "redis-replica-deploymentconfig.yaml" created INFO OpenShift file "redis-replica-imagestream.yaml" created ``` Then you can use `kubectl apply` to create these resources in OpenShift cluster. **Access the newly deployed service:** After deployment, you must create an OpenShift route in order to access the service. If you're using `minishift`, you'll use a combination of `oc` and `minishift` commands to access the service. Create a route for the `frontend` service using `oc`: ```sh $ oc expose service/frontend route "frontend" exposed ``` Access the `frontend` service with `minishift`: ```sh $ minishift openshift service frontend --namespace=myproject Opening the service myproject/frontend in the default browser... ``` You can also access the GUI interface of OpenShift for an overview of the deployed containers: ```sh $ minishift console Opening the OpenShift Web console in the default browser... ``` ## RHEL and Kompose In this guide, we'll deploy a sample `compose.yaml` file using both RHEL (Red Hat Enterprise Linux) and OpenShift. Requirements: - Red Hat Enterprise Linux 7.4 - [Red Hat Development Suite](https://developers.redhat.com/products/devsuite/overview/) - Which includes: - [minishift](https://github.com/minishift/minishift) - [kompose](https://github.com/kubernetes/kompose) **Note:** A KVM hypervisor must be setup in order to correctly use `minishift` on RHEL. You can set it up via the [CDK Documentation](https://access.redhat.com/documentation/en-us/red_hat_container_development_kit/3.1/html-single/getting_started_guide/index#setup-virtualization) under "Set up your virtualization environment". **Install Red Hat Development Suite:** Before we are able to use both `minishift` and `kompose`, DevSuite must be installed. A more concise [installation document is available](https://developers.redhat.com/products/cdk/hello-world#fndtn-rhel). Change to root. ```sh $ su - ``` Enable the Red Hat Developer Tools software repository. ```sh $ subscription-manager repos --enable rhel-7-server-devtools-rpms $ subscription-manager repos --enable rhel-server-rhscl-7-rpms ``` Add the Red Hat Developer Tools key to your system. ```sh $ cd /etc/pki/rpm-gpg $ wget -O RPM-GPG-KEY-redhat-devel https://www.redhat.com/security/data/a5787476.txt $ rpm --import RPM-GPG-KEY-redhat-devel ``` Install Red Hat Development Suite and Kompose. ```sh $ yum install rh-devsuite kompose -y ``` **Start `minishift`:** Before we begin, we must do a few preliminary steps setting up `minishift`. ```sh $ su - $ ln -s /var/lib/cdk-minishift-3.0.0/minishift /usr/bin/minishift $ minishift setup-cdk --force --default-vm-driver="kvm" $ ln -s /home/$(whoami)/.minishift/cache/oc/v3.5.5.8/oc /usr/bin/oc ``` Now we may start `minishift`. ```sh $ minishift start Starting local OpenShift cluster using 'kvm' hypervisor... -- Checking OpenShift client ... OK -- Checking Docker client ... OK -- Checking Docker version ... OK -- Checking for existing OpenShift container ... OK ... ``` **Download an [example Compose file](https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml), or use your own:** ```sh wget https://raw.githubusercontent.com/kubernetes/kompose/main/examples/compose.yaml ``` **Convert your Compose file to OpenShift:** Run `kompose convert --provider=openshift` in the same directory as your `compose.yaml` file. ```sh $ kompose convert --provider=openshift INFO OpenShift file "frontend-service.yaml" created INFO OpenShift file "redis-leader-service.yaml" created INFO OpenShift file "redis-replica-service.yaml" created INFO OpenShift file "frontend-deploymentconfig.yaml" created INFO OpenShift file "frontend-imagestream.yaml" created INFO OpenShift file "redis-leader-deploymentconfig.yaml" created INFO OpenShift file "redis-leader-imagestream.yaml" created INFO OpenShift file "redis-replica-deploymentconfig.yaml" created INFO OpenShift file "redis-replica-imagestream.yaml" created ``` Then you can use `kubectl apply` to create these resources in OpenShift. **Access the newly deployed service:** After deployment, you must create an OpenShift route in order to access the service. If you're using `minishift`, you'll use a combination of `oc` and `minishift` commands to access the service. Create a route for the `frontend` service using `oc`: ```sh $ oc expose service/frontend route "frontend" exposed ``` Access the `frontend` service with `minishift`: ```sh $ minishift openshift service frontend --namespace=myproject Opening the service myproject/frontend in the default browser... ``` You can also access the GUI interface of OpenShift for an overview of the deployed containers: ```sh $ minishift console Opening the OpenShift Web console in the default browser... ``` ================================================ FILE: docs/index.md ================================================ --- # Feel free to add content and custom Front Matter to this file. # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults layout: index --- ```sh $ kompose convert -f compose.yaml $ kubectl apply -f . $ kubectl get po NAME READY STATUS RESTARTS AGE frontend-591253677-5t038 1/1 Running 0 10s redis-leader-2410703502-9hshf 1/1 Running 0 10s redis-replica-4049176185-hr1lr 1/1 Running 0 10s ``` ================================================ FILE: docs/installation.md ================================================ --- layout: default permalink: /installation/ title: Installation redirect_from: - /docs/installation.md/ - /docs/installation/ --- # Installation * TOC {:toc} We have multiple ways to install Kompose. Our preferred (and most up-to-date) method is downloading the binary from the latest GitHub release. ## GitHub release Kompose is released via GitHub, you can see all current releases on the [GitHub release page](https://github.com/kubernetes/kompose/releases). This is the **recommended** way of installing Kompose. **Linux and macOS:** ```sh # Linux curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-linux-amd64 -o kompose # Linux ARM64 curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-linux-arm64 -o kompose # macOS curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-darwin-amd64 -o kompose # macOS ARM64 curl -L https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-darwin-arm64 -o kompose chmod +x kompose sudo mv ./kompose /usr/local/bin/kompose ``` **Windows:** Download from [GitHub](https://github.com/kubernetes/kompose/releases/download/v1.38.0/kompose-windows-amd64.exe) and add the binary to your PATH. ## Go Installing using `go install` pulls from the main branch with the latest development changes. ```sh go install github.com/kubernetes/kompose@latest ``` ## CentOS Kompose is in [EPEL](https://fedoraproject.org/wiki/EPEL) (Available in EPEL 7 package repository) CentOS repository. If you don't have [EPEL](https://fedoraproject.org/wiki/EPEL) repository already installed and enabled you can do it by running `sudo yum install epel-release` If you have [EPEL](https://fedoraproject.org/wiki/EPEL) enabled in your system, you can install Kompose like any other package. ```bash sudo yum -y install kompose ``` ## macOS On macOS, you can install the latest release via [Homebrew](https://brew.sh) or [MacPorts](https://www.macports.org/). ```bash brew install kompose ``` ## Windows Kompose can be installed via [Chocolatey](https://chocolatey.org/packages/kubernetes-kompose) ```console choco install kubernetes-kompose ``` or using winget ```console winget install Kubernetes.kompose ``` ## Docker You can build an image from the official repo for [Docker](https://docs.docker.com/engine/reference/commandline/build/) or [Podman](https://docs.podman.io/en/latest/markdown/podman-build.1.html): ```bash docker build -t kompose https://github.com/kubernetes/kompose.git\#main ``` To run the built image against the current directory, run the following command: ```bash docker run --rm -it -v $PWD:/opt -w /opt kompose kompose convert ``` ================================================ FILE: docs/integrations.md ================================================ --- layout: default permalink: /integrations/ title: Integrations redirect_from: - /docs/integrations.md/ - /docs/integrations/ --- # Integrations * TOC {:toc} There are some projects out there known to use Kompose integrated in some form or another ### Kompose UI by Jad Chamoun (ICANN) and Joe Haddad (Anghami) **Description:** "A web interface to convert Compose files to Kubernetes YAML" **Link:** [https://github.com/JadCham/komposeui](https://github.com/JadCham/komposeui) ### Kompose Docker Container by Cloudfind **Description:** "A Docker container for the Kompose translator for compose" **Link:** [https://github.com/cloudfind/kompose-docker](https://github.com/cloudfind/kompose-docker) ### KPM by CoreOS **Description:** "KPM is a tool to deploy and manage application stacks on Kubernetes" **Link:** [https://github.com/coreos/kpm](https://github.com/coreos/kpm) ### Docker Image for Adobe Enterprise Manager by Adfinis SyGroup AG **Description:** "Docker Image for Adobe Enterprise Manager" **Link:** [https://github.com/adfinis-sygroup/aem-docker/tree/master](https://github.com/adfinis-sygroup/aem-docker/tree/master) ### Kompose Ansible Playbook by Chris Houseknecht (Red Hat) **Description:** "Download and unarchive the latest kompose release asset for your OS" **Link:** [https://github.com/chouseknecht/kompose-install-role](https://github.com/chouseknecht/kompose-install-role) ### Fabric8 Maven Plugin by Red Hat **Description:** "Maven is one of the widely used build tools for Java applications. The Fabric8 Maven Plugin is a maven extension that simplifies the deployment of Java applications to Kubernetes or OpenShift clusters. The main task of this plugin is to build Docker images, generate Kubernetes or OpenShift resource descriptors and run/deploy the application on Kubernetes or OpenShift cluster. The plugin has a wide range of configuration options. Compose is one of the options to bring up deployments on Kubernetes or OpenShift clusters. Technically, Fabric8 Maven Plugin processes the external compose.yml file and generates Kubernetes or OpenShift resources via Kompose." **Links:** * [Quickstart](/maven-example) * [Documentation](https://maven.fabric8.io/#compose) ================================================ FILE: docs/maven-example.md ================================================ --- layout: default permalink: /maven-example/ title: Maven Example redirect_from: - /docs/maven-example.md/ - /docs/maven-example/ --- # Fabric8 Maven Plugin + Kompose: Let's deploy a Springboot Java application with Compose file using Fabric8 Maven Plugin to Kubernetes or OpenShift. ##### Requirements - Linux or MacOS or Windows - JDK 1.7+ - [JDK Quick Installation Guide](http://openjdk.java.net/install/) - Maven 3.x+ - [Maven Installation Guide](http://www.baeldung.com/install-maven-on-windows-linux-mac) - Kompose - [Kompose Installation Guide](/installation) **1. Clone the example project from GitHub** ```bash $ git clone https://github.com/piyush1594/kompose-maven-example.git ``` Change current directory to `kompose-maven-example` directory. ```bash $ cd kompose-maven-example ``` **2. Add Fabric8 Maven Plugin to your project** ```bash $ mvn io.fabric8:fabric8-maven-plugin:3.5.28:setup ``` Add the Fabric8 Maven Plugin configuration to `pom.xml` of project. `pom.xml` is manifest or deployment descriptor file of a maven project. **3. Install Kompose through Maven** ```bash $ mvn fabric8:install ``` This command installs the `kompose` on the host. **4. Configure Fabric8 Maven Plugin to use a Compose file** ```bash io.fabric8 fabric8-maven-plugin path for docker compose file resource build ``` Add the `` and `` sections to `pom.xml` as shown in above `pom.xml` snippet. Update the `` to provide the relative path of Compose file from `pom.xml` **5. Deploy application on Kubernetes or OpenShift** Make sure that Kubernetes/OpenShift cluster or Minikube/Minishift is running. In case, if anything of this is not running, you can run Minishift to test this application by using the following command. ```bash $ minishift start ``` Below command deploys this application on Kubernetes or OpenShift. ```bash $ mvn fabric8:deploy ``` Now that your service has been deployed, let's access it by querying `pod`, `service` from Kubernetes or OpenShift. ```bash $ oc get pods NAME READY STATUS RESTARTS AGE springboot-compose-1-xl0vb 1/1 Running 0 5m springboot-compose-s2i-1-build 0/1 Completed 0 7m ``` ```bash $ oc get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE springboot-compose 172.30.205.137 8080/TCP 6m ``` Let's access the Springboot service. ```bash $ minishift openshift service --in-browser springboot-compose Created the new window in existing browser session. ``` It will open your application endpoint in default browser. ![Output-Diagram](https://github.com/kubernetes/kompose/blob/main/docs/images/kompose-maven-output-diagram.png) ================================================ FILE: docs/user-guide.md ================================================ --- layout: default permalink: /user-guide/ title: User Guide redirect_from: - /docs/user-guide.md/ - /docs/user-guide/ --- # User Guide ## Table of contents * [Kompose conversion example](#kompose-conversion-example) * [CLI Modifications](#cli-modifications) * [Labels](#labels) * [Restart Policy](#restart-policy) * [Building and Pushing Images](#building-and-pushing-images) ## Kompose Conversion Example Kompose has support for two providers: OpenShift and Kubernetes. You can choose a targeted provider using global option `--provider`. If no provider is specified, Kubernetes is set by default. ### Kubernetes ```sh $ kompose --file compose.yaml convert ``` You can also provide multiple compose files at the same time: ```sh $ kompose -f compose.yaml -f compose.yaml convert ``` When multiple compose files are provided the configuration is merged. Any configuration that is common will be over ridden by subsequent file. You can provide your compose files via environment variables as following: ```sh $ COMPOSE_FILE="compose.yaml alternative-compose.yaml" kompose convert ``` ### OpenShift ```sh $ kompose --provider openshift --file compose.yaml convert ``` ## CLI Modifications On the command line, you can modify the output of the generated YAML. For example, using alternative controllers such as [Replication Controllers](http://kubernetes.io/docs/user-guide/replication-controller/) objects, [Daemon Sets](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/), or [Statefulset](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/). Daemon Set example: ```sh $ kompose convert --controller daemonSet ``` A full list of these options can be found on `kompose convert --help`. ## Labels `kompose` supports Kompose-specific labels within the `compose.yaml` file to get you the rest of the way there. Labels are an important kompose concept as they allow you to add Kubernetes modifications without having to edit the YAML afterwards. For example, adding an init container, or a custom readiness check. | Key / Value | Description / Example | |-----|-------------| | [`kompose.controller.port.expose`](#komposecontrollerportexpose) | Expose as hostPort on the controller (not recommended) | | `Boolean` | `false` | | [`kompose.controller.type`](#komposecontrollertype) | Type of the controller | | `String` | `deployment`, `daemonset`, `replicationcontroller`, `statefulset` | | [`kompose.cronjob.backoff_limit`](#komposecronjobbackoff_limit) | Number of retries before marked as failed | | `Integer` | `6` | | [`kompose.cronjob.concurrency_policy`](#komposecronjobconcurrency_policy) | Handling of concurrent jobs | | `String` | `Forbid`, `Allow`, `Never` | | [`kompose.cronjob.schedule`](#komposecronjobschedule) | Schedule | | `String` | `1 * * * *` | | [`kompose.hpa.cpu`](#komposehpacpu) | CPU utilization percentage that triggers autoscaling | | `Percentage` | `50%` | | [`kompose.hpa.memory`](#komposehpamemory) | Memory utilization threshold that triggers autoscaling | | `String` | `200Mi` | | [`kompose.hpa.replicas.max`](#komposehpareplicasmax) | Max pod replicas for Horizontal Pod Autoscaler | | `Integer` | `10` | | [`kompose.hpa.replicas.min`](#komposehpareplicasmin) | Min pod replicas for Horizontal Pod Autoscaler | | `Integer` | `2` | | [`kompose.image-pull-policy`](#komposeimage-pull-policy) | Policy for pulling images | | `String` | `Always`, `IfNotPresent`, `Never` | | [`kompose.image-pull-secret`](#komposeimage-pull-secret) | Secret to be used for pulling images from a private registry | | `String` | `myregistrykey` | | [`kompose.init.containers.command`](#komposeinitcontainerscommand) | Command to be executed | | `Array` | `["printenv"]` | | [`kompose.init.containers.image`](#komposeinitcontainersimage) | Image to be used | | `String` | `busybox` | | [`kompose.init.containers.name`](#komposeinitcontainersname) | Name assigned | | `String` | `init-mydb` | | [`kompose.security-context.fsgroup`](#komposesecurity-contextfsgroup) | Filesystem group ID for the pods' volumes | | `Integer` | `1001` | | [`kompose.service.external-traffic-policy`](#komposeserviceexternal-traffic-policy) | Policy to route external traffic | | `String` | `cluster`, `local` | | [`kompose.service.expose`](#komposeserviceexpose) | Creates a Ingress or Route. Accepts domain or 'true' for auto-generating a domain. | | `String` | `true,domain1.com,domain2.com` | | [`kompose.service.expose.ingress-class-name`](#komposeserviceexposeingress-class-name) | Ingress class to be used for exposing services | | `String` | `nginx` | | [`kompose.service.expose.tls-secret`](#komposeserviceexposetls-secret) | TLS secret for securing ingress | | `String` | `my-tls-secret` | | [`kompose.service.group`](#komposeservicegroup) | Label to group multiple containers in a single pod | | `String` | `mygroup` | | [`kompose.service.healthcheck.liveness.http_get_path`](#komposeservicehealthchecklivenesshttp_get_path) | HTTP GET path for liveness probe | | `String` | `/health` | | [`kompose.service.healthcheck.liveness.http_get_port`](#komposeservicehealthchecklivenesshttp_get_port) | HTTP GET port for liveness probe | | `Integer` | `8080` | | [`kompose.service.healthcheck.liveness.tcp_port`](#komposeservicehealthchecklivenesstcp_port) | TCP socket port for liveness probe | | `Integer` | `3306` | | [`kompose.service.healthcheck.readiness.disable`](#komposeservicehealthcheckreadinessdisable) | Whether to disable the readiness probe | | `Boolean` | `true` | | [`kompose.service.healthcheck.readiness.http_get_path`](#komposeservicehealthcheckreadinesshttp_get_path) | HTTP GET path for readiness probe | | `String` | `/ready` | | [`kompose.service.healthcheck.readiness.http_get_port`](#komposeservicehealthcheckreadinesshttp_get_port) | HTTP GET port for readiness probe | | `Integer` | `8081` | | [`kompose.service.healthcheck.readiness.interval`](#komposeservicehealthcheckreadinessinterval) | Interval between readiness checks | | `Duration` | `10s` | | [`kompose.service.healthcheck.readiness.retries`](#komposeservicehealthcheckreadinessretries) | Number of times readiness probe should retry before failing | | `Integer` | `3` | | [`kompose.service.healthcheck.readiness.start_period`](#komposeservicehealthcheckreadinessstart_period) | Initial delay before starting the readiness probe | | `Duration` | `30s` | | [`kompose.service.healthcheck.readiness.tcp_port`](#komposeservicehealthcheckreadinesstcp_port) | TCP socket port for readiness probe | | `Integer` | `3307` | | [`kompose.service.healthcheck.readiness.test`](#komposeservicehealthcheckreadinesstest) | Command or script run by the readiness probe | | `Array` | `["CMD", "echo", "OK"]` | | [`kompose.service.healthcheck.readiness.timeout`](#komposeservicehealthcheckreadinesstimeout) | Timeout for a single readiness probe | | `Duration` | `5s` | | [`kompose.service.nodeport.port`](#komposeservicenodeportport) | Specific port number to be used as NodePort | | `Integer` | `30000` | | [`kompose.service.type`](#komposeservicetype) | Type of service | | `String` | `nodeport`, `clusterip`, `loadbalancer`, `headless` | | [`kompose.volume.size`](#komposevolumesize) | Size of the volume | | `String` | `1Gi` | | [`kompose.volume.storage-class-name`](#komposevolumestorage-class-name) | StorageClassName for provisioning volumes | | `String` | `standard` | | [`kompose.volume.subpath`](#komposevolumesubpath) | Subpath inside the mounted volume | | `String` | `/data` | | [`kompose.volume.type`](#komposevolumetype) | Type of Kubernetes volume | | `String` | `configMap`, `persistentVolumeClaim`, `emptyDir`, `hostPath` | ### kompose.controller.port.expose ```yaml services: web: image: wordpress:latest ports: - '80:80' labels: kompose.controller.expose.port: true ``` ### kompose.controller.type ```yaml services: web: image: wordpress:latest ports: - '80:80' labels: kompose.controller.type: deployment ``` ### kompose.cronjob.backoff_limit ```yaml services: cron-job: image: busybox labels: kompose.cronjob.backoff_limit: 3 ``` ### kompose.cronjob.concurrency_policy ```yaml services: periodic-task: image: busybox labels: kompose.cronjob.concurrency_policy: Forbid ``` ### kompose.cronjob.schedule ```yaml services: cron-job: image: busybox labels: kompose.cronjob.schedule: "*/5 * * * *" ``` ### kompose.hpa.cpu ```yaml services: web: image: nginx labels: kompose.hpa.cpu: 80 ``` ### kompose.hpa.memory ```yaml services: db: image: mysql labels: kompose.hpa.memory: 512Mi ``` ### kompose.hpa.replicas.max ```yaml services: api: image: custom-api labels: kompose.hpa.replicas.max: 10 ``` ### kompose.hpa.replicas.min ```yaml services: api: image: custom-api labels: kompose.hpa.replicas.min: 2 ``` ### kompose.image-pull-policy ```yaml services: example-service: image: example-image labels: kompose.image-pull-policy: "IfNotPresent" ``` ### kompose.image-pull-secret ```yaml services: private-service: image: private-repo/image:tag labels: kompose.image-pull-secret: "my-private-registry-key" ``` ### kompose.init.containers.command ```yaml services: init-service: image: busybox labels: kompose.init.containers.command: ["echo", "Initializing..."] ``` ### kompose.init.containers.image ```yaml services: init-service: image: busybox labels: kompose.init.containers.image: busybox ``` ### kompose.init.containers.name ```yaml services: init-service: image: busybox labels: kompose.init.containers.name: "initial-setup" ``` ### kompose.security-context.fsgroup ```yaml services: secured-service: image: nginx labels: kompose.security-context.fsgroup: 2000 ``` ### kompose.service.external-traffic-policy ```yaml services: front-end: image: quay.io/kompose/web ports: - 8080:8080 labels: kompose.service.external-traffic-policy: local ``` ### kompose.service.expose ```yaml services: web-app: image: nginx ports: - 80:80 labels: kompose.service.expose: "example.com" ``` ### kompose.service.expose.ingress-class-name ```yaml services: web: image: nginx ports: - 80:80 labels: kompose.service.expose.ingress-class-name: "nginx" ``` ### kompose.service.expose.tls-secret ```yaml services: web: image: nginx ports: - 443:443 labels: kompose.service.expose.tls-secret: "my-ssl-secret" ``` ### kompose.service.group ```yaml version: "3" services: nginx: image: nginx depends_on: - logs labels: - kompose.service.group=sidecar logs: image: busybox command: ["tail -f /var/log/nginx/access.log"] labels: - kompose.service.group=sidecar ``` ### kompose.service.healthcheck.liveness.http_get_path ```yaml services: web: image: custom-web ports: - "8080:8080" labels: kompose.service.healthcheck.liveness.http_get_path: /health ``` ### kompose.service.healthcheck.liveness.http_get_port ```yaml services: web: image: custom-web ports: - "8080:8080" labels: kompose.service.healthcheck.liveness.http_get_port: 8080 ``` ### kompose.service.healthcheck.liveness.tcp_port ```yaml services: db: image: mysql ports: - "3306:3306" labels: kompose.service.healthcheck.liveness.tcp_port: 3306 ``` ### kompose.service.healthcheck.readiness.disable ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.disable: true ``` ### kompose.service.healthcheck.readiness.http_get_path ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.http_get_path: /ready ``` ### kompose.service.healthcheck.readiness.http_get_port ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.http_get_port: 8081 ``` ### kompose.service.healthcheck.readiness.interval ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.interval: 10s ``` ### kompose.service.healthcheck.readiness.retries ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.retries: 3 ``` ### kompose.service.healthcheck.readiness.start_period ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.start_period: 30s ``` ### kompose.service.healthcheck.readiness.tcp_port ```yaml services: db: image: mysql labels: kompose.service.healthcheck.readiness.tcp_port: 3307 ``` ### kompose.service.healthcheck.readiness.test ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.test: ["CMD", "curl", "-f", "http://localhost:8081/ready"] ``` ### kompose.service.healthcheck.readiness.timeout ```yaml services: web: image: custom-web labels: kompose.service.healthcheck.readiness.timeout: 5s ``` ### kompose.service.nodeport.port ```yaml services: web: image: nginx ports: - "30000:80" labels: kompose.service.nodeport.port: 30000 ``` ### kompose.service.type ```yaml services: web: image: nginx ports: - "80:80" labels: kompose.service.type: nodeport ``` ### kompose.volume.size ```yaml services: db: image: postgres:10.1 labels: kompose.volume.size: 1Gi volumes: - db-data:/var/lib/postgresql/data ``` ### kompose.volume.storage-class-name ```yaml services: db: image: postgres:10.1 labels: kompose.volume.storage-class-name: custom-storage-class-name volumes: - db-data:/var/lib/postgresql/data ``` ### kompose.volume.subpath ```yaml services: pgadmin: image: postgres labels: kompose.volume.subpath: pg-data ``` ### kompose.volume.type ```yaml services: db: image: postgres labels: kompose.volume.type: persistentVolumeClaim volumes: - db-data:/var/lib/postgresql/data ``` ## Restart Policy If you want to create normal pods without a controller you can use the `restart` construct of compose to define that. Follow the table below to see what happens on the `restart` value. | `compose` `restart` | object created | Pod `restartPolicy` | |---------------------|-------------------|---------------------| | `""` | controller object | `Always` | | `always` | controller object | `Always` | | `unless-stopped` | controller object | `Always` | | `on-failure` | Pod / CronJob | `OnFailure` | | `no` | Pod / CronJob | `Never` | **Note**: controller object could be `deployment`, `replicationcontroller`, etc. For example, the `pival` service will become a pod down here. This container calculated the value of `pi`. ```yaml version: '2' services: pival: image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restart: "on-failure" ``` For example, the `pival` service will become a cron job down here. This container calculated the value of `pi` every minute. ```yaml version: '2' services: pival: image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restart: "no" labels: kompose.cronjob.schedule: "* * * * *" kompose.cronjob.concurrency_policy: "Forbid" kompose.cronjob.backoff_limit: "0" ``` #### Warning about Deployment Configs If the Compose file has a volume specified for a service, the Deployment (Kubernetes) or DeploymentConfig (OpenShift) strategy is changed to "Recreate" instead of "RollingUpdate" (default). This is done to avoid multiple instances of a service from accessing a volume at the same time. If the Compose file has a service name with `_` or `.` in it (e.g., `web_service` or `web.service`), then it will be replaced by `-` and the service name will be renamed accordingly (e.g., `web-service`). Kompose does this because "Kubernetes" doesn't allow `_` in object names. Please note that changing the service name might break some `compose` files. ## Building and Pushing Images If the Compose file has `build` or `build:context, build:dockerfile` keys, build will run when `--build` specified. And Image will push to _docker.io_ (default) when `--push-image=true` specified. It is possible to push to a custom registry by specifying `--push-image-registry`, which will override the registry from the image name. ### Authentication on Registry Kompose uses the docker authentication from file `$DOCKER_CONFIG/config.json`, `$HOME/.docker/config.json`, and `$HOME/.dockercfg` after `docker login`. **This only works fine on Linux but macOS would fail when using `"credsStore": "osxkeychain"`.** However, there is an approach to push successfully on macOS, by not using `osxkeychain` for `credsStore`. To disable `osxkeychain`: - remove `credsStore` from the `config.json` file, and `docker login` again. - for some docker desktop versions, there is a setting `Securely store Docker logins in macOS keychain`, which should be unchecked. Then restart docker desktop if needed, and `docker login` again. Now `config.json` should contain base64 encoded passwords, then push image should succeed. Working, but not safe though! Use it at your risk. For Windows, there is also `credsStore` which is `wincred`. Technically it will fail on authentication as macOS does, but you can try the approach above like macOS too. ### Custom Build and Push If you want to customize the build and push processes and use another containers solution than Docker, Kompose offers you the possibility to do that. You can use `--build-command` and `--push-command` flags to achieve that. e.g: `kompose -f convert --build-command 'whatever command --you-use' --push-command 'whatever command --you-use'` ================================================ FILE: examples/compose.yaml ================================================ services: redis-leader: container_name: redis-leader image: redis ports: - "6379" redis-replica: container_name: redis-replica image: redis ports: - "6379" command: redis-server --replicaof redis-leader 6379 --dir /tmp web: container_name: web image: quay.io/kompose/web ports: - "8080:8080" ================================================ FILE: examples/web/Dockerfile ================================================ FROM golang:1.21.2 # Debugging within the container RUN apt-get update && apt-get install dnsutils redis-tools -y # Set the working directory in the container WORKDIR /app # Copy the entire project which includes the public directory, vendoring, etc. COPY . . # Build your application RUN CGO_ENABLED=0 GOOS=linux go build -o /frontend # Change the permissions so that all users can execute it RUN chmod +x /frontend # Although setting permissions on /frontend should suffice, set wider permissions if needed RUN chown -R 1001:0 /app && \ chmod -R g=u /app # This directive ensures the container does not run as root USER 1001 EXPOSE 8080 CMD ["/frontend"] ================================================ FILE: examples/web/README.md ================================================ A fork of https://github.com/kubernetes/examples/blob/master/guestbook-go/README.md A simple example that shows the functionality of Kompose Pushed to https://docker.io/kompose/web ================================================ FILE: examples/web/compose.yaml ================================================ services: redis-leader: container_name: redis-leader image: redis ports: - "6379" redis-replica: container_name: redis-replica image: redis ports: - "6379" command: redis-server --replicaof redis-leader 6379 web: container_name: web build: ./web ports: - "8080:8080" ================================================ FILE: examples/web/go.mod ================================================ module github.com/redhat-developer/podman-desktop-demo go 1.21.2 require ( github.com/codegangsta/negroni v1.0.0 github.com/gorilla/mux v1.8.1 github.com/xyproto/simpleredis/v2 v2.6.5 ) require ( github.com/gomodule/redigo v1.8.9 // indirect github.com/xyproto/pinterface v1.5.3 // indirect ) ================================================ FILE: examples/web/go.sum ================================================ github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xyproto/pinterface v1.5.3 h1:RKkNT88cwrSqD9hU4cYAO5yeo8srg4TG+74Pcj88iz0= github.com/xyproto/pinterface v1.5.3/go.mod h1:X5B5pKE49ak7SpyDh5QvJvLH9cC9XuZNDcl5hEyYc34= github.com/xyproto/simpleredis/v2 v2.6.5 h1:PtAz5j2UUACNHx5LetI60dbCsVMhIv1H878bND4VQK4= github.com/xyproto/simpleredis/v2 v2.6.5/go.mod h1:OrWVubZGr0SbpAjkAQkFn/iiex2AsblBm80uhYxbjQY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: examples/web/main.go ================================================ package main import ( "encoding/json" "net/http" "os" "strings" "github.com/codegangsta/negroni" "github.com/gorilla/mux" "github.com/xyproto/simpleredis/v2" ) var ( leaderPool *simpleredis.ConnectionPool replicaPools []*simpleredis.ConnectionPool ) func ListRangeHandler(rw http.ResponseWriter, req *http.Request) { key := mux.Vars(req)["key"] var members []string var err error for _, replicaPool := range replicaPools { list := simpleredis.NewList(replicaPool, key) members, err = list.GetAll() if err == nil { break // Found a replica with data, exit loop } } if err != nil { http.Error(rw, "Failed to retrieve data from replicas", http.StatusInternalServerError) return } membersJSON, err := json.MarshalIndent(members, "", " ") if err != nil { http.Error(rw, "Failed to marshal JSON", http.StatusInternalServerError) return } rw.Write(membersJSON) } func ListPushHandler(rw http.ResponseWriter, req *http.Request) { key := mux.Vars(req)["key"] value := mux.Vars(req)["value"] list := simpleredis.NewList(leaderPool, key) HandleError(nil, list.Add(value)) ListRangeHandler(rw, req) } func InfoHandler(rw http.ResponseWriter, req *http.Request) { info := HandleError(leaderPool.Get(0).Do("INFO")).([]byte) rw.Write(info) } func EnvHandler(rw http.ResponseWriter, req *http.Request) { environment := make(map[string]string) for _, item := range os.Environ() { splits := strings.Split(item, "=") key := splits[0] val := strings.Join(splits[1:], "=") environment[key] = val } envJSON := HandleError(json.MarshalIndent(environment, "", " ")).([]byte) rw.Write(envJSON) } func HandleError(result interface{}, err error) (r interface{}) { if err != nil { panic(err) } return result } func getReplicaPool() *simpleredis.ConnectionPool { // Use the first replica as the primary replica for read operations return replicaPools[0] } func main() { // Read the Redis replica addresses from an environment variable replicaAddresses := os.Getenv("REDIS_REPLICAS") if replicaAddresses == "" { // Use default values if not set replicaAddresses = "redis-replica" } // Read the Redis port number from an environment variable redisPort := os.Getenv("REDIS_PORT") if redisPort == "" { // Use default port if not set redisPort = "6379" } // Read the Redis leader address from an environment variable redisLeaderAddress := os.Getenv("REDIS_LEADER") if redisLeaderAddress == "" { // Use default leader address if not set redisLeaderAddress = "redis-leader" } // Read the server port number from an environment variable serverPort := os.Getenv("SERVER_PORT") if serverPort == "" { // Use default port if not set serverPort = "8080" } // Construct the Redis leader and replica addresses leaderAddress := redisLeaderAddress + ":" + redisPort replicaAddressesArr := strings.Split(replicaAddresses, ",") replicaPools = make([]*simpleredis.ConnectionPool, len(replicaAddressesArr)) for i, addr := range replicaAddressesArr { replicaPools[i] = simpleredis.NewConnectionPoolHost(addr + ":" + redisPort) defer replicaPools[i].Close() } // Create a connection pool for the leader using the constructed address leaderPool = simpleredis.NewConnectionPoolHost(leaderAddress) defer leaderPool.Close() r := mux.NewRouter() r.Path("/lrange/{key}").Methods("GET").HandlerFunc(ListRangeHandler) r.Path("/rpush/{key}/{value}").Methods("GET").HandlerFunc(ListPushHandler) r.Path("/info").Methods("GET").HandlerFunc(InfoHandler) r.Path("/env").Methods("GET").HandlerFunc(EnvHandler) n := negroni.Classic() n.UseHandler(r) n.Run(":" + serverPort) } ================================================ FILE: examples/web/public/index.html ================================================ Kompose Guestbook

    Waiting for database connection...

    ================================================ FILE: examples/web/public/script.js ================================================ $(document).ready(function() { var headerTitleElement = $("#header h1"); var entriesElement = $("#guestbook-entries"); var formElement = $("#guestbook-form"); var submitElement = $("#guestbook-submit"); var entryContentElement = $("#guestbook-entry-content"); var hostAddressElement = $("#guestbook-host-address"); var appendGuestbookEntries = function(data) { entriesElement.empty(); $.each(data, function(key, val) { entriesElement.append("

    " + val + "

    "); }); } // On submission, clear the input field and add the entry to the guestbook. var handleSubmission = function(e) { e.preventDefault(); var entryValue = entryContentElement.val() if (entryValue.length > 0) { entriesElement.append("

    ...

    "); $.getJSON("rpush/guestbook/" + entryValue, appendGuestbookEntries); } // Clear entryContentElement input field on click. entryContentElement.val(""); return false; } var colors = ["#1f77b4", "#2ca02c", "#d62728", "#9467bd", "#ff7f0e"]; var randomColor = colors[Math.floor(5 * Math.random())]; (function setElementsColor(color) { headerTitleElement.css("color", color); entryContentElement.css("box-shadow", "inset 0 0 0 2px " + color); submitElement.css("background-color", color); })(randomColor); submitElement.click(handleSubmission); formElement.submit(handleSubmission); hostAddressElement.append(document.URL); // Poll every second. (function fetchGuestbook() { $.getJSON("lrange/guestbook").done(appendGuestbookEntries).always( function() { setTimeout(fetchGuestbook, 1000); }); })(); }); ================================================ FILE: examples/web/public/style.css ================================================ body, input { color: #123; font-family: "Gill Sans", sans-serif; } div { overflow: hidden; padding: 1em 0; position: relative; text-align: center; } h1, h2, p, input, a { font-weight: 300; margin: 0; } h1 { color: #BDB76B; font-size: 3.5em; } h2 { color: #999; } form { margin: 0 auto; max-width: 50em; text-align: center; } input { border: 0; border-radius: 1000px; box-shadow: inset 0 0 0 2px #BDB76B; display: inline; font-size: 1.5em; margin-bottom: 1em; outline: none; padding: .5em 5%; width: 55%; } form a { background: #BDB76B; border: 0; border-radius: 1000px; color: #FFF; font-size: 1.25em; font-weight: 400; padding: .75em 2em; text-decoration: none; text-transform: uppercase; white-space: normal; } p { font-size: 1.5em; line-height: 1.5; } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/.gitignore ================================================ /coverage.txt ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/.travis.yml ================================================ language: go sudo: false dist: trusty go: - 1.x - 1.2.x - 1.3.x - 1.4.x - 1.5.x - 1.6.x - 1.7.x - 1.8.x - master before_install: - find "${GOPATH%%:*}" -name '*.a' -delete - rm -rf "${GOPATH%%:*}/src/golang.org" - go get golang.org/x/tools/cover - go get golang.org/x/tools/cmd/cover script: - go test -race -coverprofile=coverage.txt -covermode=atomic after_success: - bash <(curl -s "https://codecov.io/bash") ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/CHANGELOG.md ================================================ # Change Log **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] - ## [1.0.0] - 2018-09-01 ### Fixed - `Logger` middleware now correctly handles paths containing a `%` instead of trying to treat it as a format specifier ## [0.3.0] - 2017-11-11 ### Added - `With()` helper for building a new `Negroni` struct chaining handlers from existing `Negroni` structs - Format log output in `Logger` middleware via a configurable `text/template` string injectable via `.SetFormat`. Added `LoggerDefaultFormat` and `LoggerDefaultDateFormat` to configure the default template and date format used by the `Logger` middleware. - Support for HTTP/2 pusher support via `http.Pusher` interface for Go 1.8+. - `WrapFunc` to convert `http.HandlerFunc` into a `negroni.Handler` - `Formatter` field added to `Recovery` middleware to allow configuring how `panic`s are output. Default of `TextFormatter` (how it was output in `0.2.0`) used. `HTMLPanicFormatter` also added to allow easy outputing of `panic`s as HTML. ### Fixed - `Written()` correct returns `false` if no response header has been written - Only implement `http.CloseNotifier` with the `negroni.ResponseWriter` if the underlying `http.ResponseWriter` implements it (previously would always implement it and panic if the underlying `http.ResponseWriter` did not. ### Changed - Set default status to `0` in the case that no handler writes status -- was previously `200` (in 0.2.0, before that it was `0` so this reestablishes that behavior) - Catch `panic`s thrown by callbacks provided to the `Recovery` handler - Recovery middleware will set `text/plain` content-type if none is set - `ALogger` interface to allow custom logger outputs to be used with the `Logger` middleware. Changes embeded field in `negroni.Logger` from `Logger` to `ALogger`. - Default `Logger` middleware output changed to be more structure and verbose (also now configurable, see `Added`) - Automatically bind to port specified in `$PORT` in `.Run()` if an address is not passed in. Fall back to binding to `:8080` if no address specified (configuable via `DefaultAddress`). - `PanicHandlerFunc` added to `Recovery` middleware to enhance custom handling of `panic`s by providing additional information to the handler including the stack and the `http.Request`. `Recovery.ErrorHandlerFunc` was also added, but deprecated in favor of the new `PanicHandlerFunc`. ## [0.2.0] - 2016-05-10 ### Added - Support for variadic handlers in `New()` - Added `Negroni.Handlers()` to fetch all of the handlers for a given chain - Allowed size in `Recovery` handler was bumped to 8k - `Negroni.UseFunc` to push another handler onto the chain ### Changed - Set the status before calling `beforeFuncs` so the information is available to them - Set default status to `200` in the case that no handler writes status -- was previously `0` - Panic if `nil` handler is given to `negroni.Use` ## 0.1.0 - 2013-07-22 ### Added - Initial implementation. [Unreleased]: https://github.com/urfave/negroni/compare/v0.2.0...HEAD [0.2.0]: https://github.com/urfave/negroni/compare/v0.1.0...v0.2.0 ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Jeremy Saenz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/README.md ================================================ # Negroni [![GoDoc](https://godoc.org/github.com/urfave/negroni?status.svg)](http://godoc.org/github.com/urfave/negroni) [![Build Status](https://travis-ci.org/urfave/negroni.svg?branch=master)](https://travis-ci.org/urfave/negroni) [![codebeat](https://codebeat.co/badges/47d320b1-209e-45e8-bd99-9094bc5111e2)](https://codebeat.co/projects/github-com-urfave-negroni) [![codecov](https://codecov.io/gh/urfave/negroni/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/negroni) **Notice:** This is the library formerly known as `github.com/codegangsta/negroni` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. Negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of `net/http` Handlers. If you like the idea of [Martini](https://github.com/go-martini/martini), but you think it contains too much magic, then Negroni is a great fit. Language Translations: * [Deutsch (de_DE)](translations/README_de_de.md) * [Português Brasileiro (pt_BR)](translations/README_pt_br.md) * [简体中文 (zh_CN)](translations/README_zh_CN.md) * [繁體中文 (zh_TW)](translations/README_zh_tw.md) * [日本語 (ja_JP)](translations/README_ja_JP.md) * [Français (fr_FR)](translations/README_fr_FR.md) ## Getting Started After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. ``` go package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.Classic() // Includes some default middlewares n.UseHandler(mux) http.ListenAndServe(":3000", n) } ``` Then install the Negroni package (**NOTE**: >= **go 1.1** is required): ``` go get github.com/urfave/negroni ``` Then run your server: ``` go run server.go ``` You will now have a Go `net/http` webserver running on `localhost:3000`. ### Packaging If you are on Debian, `negroni` is also available as [a package](https://packages.debian.org/sid/golang-github-urfave-negroni-dev) that you can install via `apt install golang-github-urfave-negroni-dev` (at the time of writing, it is in the `sid` repositories). ## Is Negroni a Framework? Negroni is **not** a framework. It is a middleware-focused library that is designed to work directly with `net/http`. ## Routing? Negroni is BYOR (Bring your own Router). The Go community already has a number of great http routers available, and Negroni tries to play well with all of them by fully supporting `net/http`. For instance, integrating with [Gorilla Mux] looks like so: ``` go router := mux.NewRouter() router.HandleFunc("/", HomeHandler) n := negroni.New(Middleware1, Middleware2) // Or use a middleware with the Use() function n.Use(Middleware3) // router goes last n.UseHandler(router) http.ListenAndServe(":3001", n) ``` ## `negroni.Classic()` `negroni.Classic()` provides some default middleware that is useful for most applications: * [`negroni.Recovery`](#recovery) - Panic Recovery Middleware. * [`negroni.Logger`](#logger) - Request/Response Logger Middleware. * [`negroni.Static`](#static) - Static File serving under the "public" directory. This makes it really easy to get started with some useful features from Negroni. ## Handlers Negroni provides a bidirectional middleware flow. This is done through the `negroni.Handler` interface: ``` go type Handler interface { ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) } ``` If a middleware hasn't already written to the `ResponseWriter`, it should call the next `http.HandlerFunc` in the chain to yield to the next middleware handler. This can be used for great good: ``` go func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { // do some stuff before next(rw, r) // do some stuff after } ``` And you can map it to the handler chain with the `Use` function: ``` go n := negroni.New() n.Use(negroni.HandlerFunc(MyMiddleware)) ``` You can also map plain old `http.Handler`s: ``` go n := negroni.New() mux := http.NewServeMux() // map your routes n.UseHandler(mux) http.ListenAndServe(":3000", n) ``` ## `With()` Negroni has a convenience function called `With`. `With` takes one or more `Handler` instances and returns a new `Negroni` with the combination of the receiver's handlers and the new handlers. ```go // middleware we want to reuse common := negroni.New() common.Use(MyMiddleware1) common.Use(MyMiddleware2) // `specific` is a new negroni with the handlers from `common` combined with the // the handlers passed in specific := common.With( SpecificMiddleware1, SpecificMiddleware2 ) ``` ## `Run()` Negroni has a convenience function called `Run`. `Run` takes an addr string identical to [`http.ListenAndServe`](https://godoc.org/net/http#ListenAndServe). ``` go package main import ( "github.com/urfave/negroni" ) func main() { n := negroni.Classic() n.Run(":8080") } ``` If no address is provided, the `PORT` environment variable is used instead. If the `PORT` environment variable is not defined, the default address will be used. See [Run](https://godoc.org/github.com/urfave/negroni#Negroni.Run) for a complete description. In general, you will want to use `net/http` methods and pass `negroni` as a `Handler`, as this is more flexible, e.g.: ``` go package main import ( "fmt" "log" "net/http" "time" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.Classic() // Includes some default middlewares n.UseHandler(mux) s := &http.Server{ Addr: ":8080", Handler: n, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } log.Fatal(s.ListenAndServe()) } ``` ## Route Specific Middleware If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler. ``` go router := mux.NewRouter() adminRoutes := mux.NewRouter() // add admin routes here // Create a new negroni for the admin middleware router.PathPrefix("/admin").Handler(negroni.New( Middleware1, Middleware2, negroni.Wrap(adminRoutes), )) ``` If you are using [Gorilla Mux], here is an example using a subrouter: ``` go router := mux.NewRouter() subRouter := mux.NewRouter().PathPrefix("/subpath").Subrouter().StrictSlash(true) subRouter.HandleFunc("/", someSubpathHandler) // "/subpath/" subRouter.HandleFunc("/:id", someSubpathHandler) // "/subpath/:id" // "/subpath" is necessary to ensure the subRouter and main router linkup router.PathPrefix("/subpath").Handler(negroni.New( Middleware1, Middleware2, negroni.Wrap(subRouter), )) ``` `With()` can be used to eliminate redundancy for middlewares shared across routes. ``` go router := mux.NewRouter() apiRoutes := mux.NewRouter() // add api routes here webRoutes := mux.NewRouter() // add web routes here // create common middleware to be shared across routes common := negroni.New( Middleware1, Middleware2, ) // create a new negroni for the api middleware // using the common middleware as a base router.PathPrefix("/api").Handler(common.With( APIMiddleware1, negroni.Wrap(apiRoutes), )) // create a new negroni for the web middleware // using the common middleware as a base router.PathPrefix("/web").Handler(common.With( WebMiddleware1, negroni.Wrap(webRoutes), )) ``` ## Bundled Middleware ### Static This middleware will serve files on the filesystem. If the files do not exist, it proxies the request to the next middleware. If you want the requests for non-existent files to return a `404 File Not Found` to the user you should look at using [http.FileServer](https://golang.org/pkg/net/http/#FileServer) as a handler. Example: ``` go package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) // Example of using a http.FileServer if you want "server-like" rather than "middleware" behavior // mux.Handle("/public", http.FileServer(http.Dir("/home/public"))) n := negroni.New() n.Use(negroni.NewStatic(http.Dir("/tmp"))) n.UseHandler(mux) http.ListenAndServe(":3002", n) } ``` Will serve files from the `/tmp` directory first, but proxy calls to the next handler if the request does not match a file on the filesystem. ### Recovery This middleware catches `panic`s and responds with a `500` response code. If any other middleware has written a response code or body, this middleware will fail to properly send a 500 to the client, as the client has already received the HTTP response code. Additionally, an `PanicHandlerFunc` can be attached to report 500's to an error reporting service such as Sentry or Airbrake. Example: ``` go package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() n.Use(negroni.NewRecovery()) n.UseHandler(mux) http.ListenAndServe(":3003", n) } ``` Will return a `500 Internal Server Error` to each request. It will also log the stack traces as well as print the stack trace to the requester if `PrintStack` is set to `true` (the default). Example with error handler: ``` go package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() recovery := negroni.NewRecovery() recovery.PanicHandlerFunc = reportToSentry n.Use(recovery) n.UseHandler(mux) http.ListenAndServe(":3003", n) } func reportToSentry(info *negroni.PanicInformation) { // write code here to report error to Sentry } ``` The middleware simply output the informations on STDOUT by default. You can customize the output process by using the `SetFormatter()` function. You can use also the `HTMLPanicFormatter` to display a pretty HTML when a crash occurs. ``` go package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() recovery := negroni.NewRecovery() recovery.Formatter = &negroni.HTMLPanicFormatter{} n.Use(recovery) n.UseHandler(mux) http.ListenAndServe(":3003", n) } ``` ## Logger This middleware logs each incoming request and response. Example: ``` go package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.New() n.Use(negroni.NewLogger()) n.UseHandler(mux) http.ListenAndServe(":3004", n) } ``` Will print a log similar to: ``` [negroni] 2017-10-04T14:56:25+02:00 | 200 | 378µs | localhost:3004 | GET / ``` on each request. You can also set your own log format by calling the `SetFormat` function. The format is a template string with fields as mentioned in the `LoggerEntry` struct. So, as an example - ```go l.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}") ``` will show something like - `[200 18.263µs] - Go-User-Agent/1.1 ` ## Third Party Middleware Here is a current list of Negroni compatible middlware. Feel free to put up a PR linking your middleware if you have built one: | Middleware | Author | Description | | -----------|--------|-------------| | [authz](https://github.com/casbin/negroni-authz) | [Yang Luo](https://github.com/hsluoyz) | ACL, RBAC, ABAC Authorization middlware based on [Casbin](https://github.com/casbin/casbin) | | [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs | | [cloudwatch](https://github.com/cvillecsteele/negroni-cloudwatch) | [Colin Steele](https://github.com/cvillecsteele) | AWS cloudwatch metrics middleware | | [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | | [csp](https://github.com/awakenetworks/csp) | [Awake Networks](https://github.com/awakenetworks) | [Content Security Policy](https://www.w3.org/TR/CSP2/) (CSP) support | | [delay](https://github.com/jeffbmartinez/delay) | [Jeff Martinez](https://github.com/jeffbmartinez) | Add delays/latency to endpoints. Useful when testing effects of high latency | | [New Relic Go Agent](https://github.com/yadvendar/negroni-newrelic-go-agent) | [Yadvendar Champawat](https://github.com/yadvendar) | Official [New Relic Go Agent](https://github.com/newrelic/go-agent) (currently in beta) | | [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime | | [Graceful](https://github.com/tylerb/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown | | [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression | | [JWT Middleware](https://github.com/auth0/go-jwt-middleware) | [Auth0](https://github.com/auth0) | Middleware checks for a JWT on the `Authorization` header on incoming requests and decodes it| | [JWT Middleware](https://github.com/mfuentesg/go-jwtmiddleware) | [Marcelo Fuentes](https://github.com/mfuentesg) | JWT middleware for golang | | [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger | | [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware | | [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly | | [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions | | [prometheus](https://github.com/zbindenren/negroni-prometheus) | [Rene Zbinden](https://github.com/zbindenren) | Easily create metrics endpoint for the [prometheus](http://prometheus.io) instrumentation tool | | [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates | | [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints | | [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins | | [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management | | [stats](https://github.com/thoas/stats) | [Florent Messa](https://github.com/thoas) | Store information about your web application (response time, etc.) | | [VanGoH](https://github.com/auroratechnologies/vangoh) | [Taylor Wrobel](https://github.com/twrobel3) | Configurable [AWS-Style](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html) HMAC authentication middleware | | [xrequestid](https://github.com/pilu/xrequestid) | [Andrea Franz](https://github.com/pilu) | Middleware that assigns a random X-Request-Id header to each request | | [mgo session](https://github.com/joeljames/nigroni-mgo-session) | [Joel James](https://github.com/joeljames) | Middleware that handles creating and closing mgo sessions per request | | [digits](https://github.com/bamarni/digits) | [Bilal Amarni](https://github.com/bamarni) | Middleware that handles [Twitter Digits](https://get.digits.com/) authentication | | [stats](https://github.com/guptachirag/stats) | [Chirag Gupta](https://github.com/guptachirag/stats) | Middleware that manages qps and latency stats for your endpoints and asynchronously flushes them to influx db | | [Chaos](https://github.com/falzm/chaos) | [Marc Falzon](https://github.com/falzm) | Middleware for injecting chaotic behavior into application in a programmatic way | ## Examples [Alexander Rødseth](https://github.com/xyproto) created [mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a Negroni middleware handler. [Prasanga Siripala](https://github.com/pjebs) created an effective skeleton structure for web-based Go/Negroni projects: [Go-Skeleton](https://github.com/pjebs/go-skeleton) ## Live code reload? [gin](https://github.com/codegangsta/gin) and [fresh](https://github.com/pilu/fresh) both live reload negroni apps. ## Essential Reading for Beginners of Go & Negroni * [Using a Context to pass information from middleware to end handler](http://elithrar.github.io/article/map-string-interface/) * [Understanding middleware](https://mattstauffer.co/blog/laravel-5.0-middleware-filter-style) ## About Negroni is obsessively designed by none other than the [Code Gangsta](https://codegangsta.io/) [Gorilla Mux]: https://github.com/gorilla/mux [`http.FileSystem`]: https://godoc.org/net/http#FileSystem ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/doc.go ================================================ // Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers. // // If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit. // // For a full guide visit http://github.com/urfave/negroni // // package main // // import ( // "github.com/urfave/negroni" // "net/http" // "fmt" // ) // // func main() { // mux := http.NewServeMux() // mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { // fmt.Fprintf(w, "Welcome to the home page!") // }) // // n := negroni.Classic() // n.UseHandler(mux) // n.Run(":3000") // } package negroni ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/logger.go ================================================ package negroni import ( "bytes" "log" "net/http" "os" "text/template" "time" ) // LoggerEntry is the structure passed to the template. type LoggerEntry struct { StartTime string Status int Duration time.Duration Hostname string Method string Path string Request *http.Request } // LoggerDefaultFormat is the format logged used by the default Logger instance. var LoggerDefaultFormat = "{{.StartTime}} | {{.Status}} | \t {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}}" // LoggerDefaultDateFormat is the format used for date by the default Logger instance. var LoggerDefaultDateFormat = time.RFC3339 // ALogger interface type ALogger interface { Println(v ...interface{}) Printf(format string, v ...interface{}) } // Logger is a middleware handler that logs the request as it goes in and the response as it goes out. type Logger struct { // ALogger implements just enough log.Logger interface to be compatible with other implementations ALogger dateFormat string template *template.Template } // NewLogger returns a new Logger instance func NewLogger() *Logger { logger := &Logger{ALogger: log.New(os.Stdout, "[negroni] ", 0), dateFormat: LoggerDefaultDateFormat} logger.SetFormat(LoggerDefaultFormat) return logger } func (l *Logger) SetFormat(format string) { l.template = template.Must(template.New("negroni_parser").Parse(format)) } func (l *Logger) SetDateFormat(format string) { l.dateFormat = format } func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { start := time.Now() next(rw, r) res := rw.(ResponseWriter) log := LoggerEntry{ StartTime: start.Format(l.dateFormat), Status: res.Status(), Duration: time.Since(start), Hostname: r.Host, Method: r.Method, Path: r.URL.Path, Request: r, } buff := &bytes.Buffer{} l.template.Execute(buff, log) l.Println(buff.String()) } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/negroni.go ================================================ package negroni import ( "log" "net/http" "os" ) const ( // DefaultAddress is used if no other is specified. DefaultAddress = ":8080" ) // Handler handler is an interface that objects can implement to be registered to serve as middleware // in the Negroni middleware stack. // ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc // passed in. // // If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked. type Handler interface { ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) } // HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers. // If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f. type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { h(rw, r, next) } type middleware struct { handler Handler next *middleware } func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) { m.handler.ServeHTTP(rw, r, m.next.ServeHTTP) } // Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni // middleware. The next http.HandlerFunc is automatically called after the Handler // is executed. func Wrap(handler http.Handler) Handler { return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { handler.ServeHTTP(rw, r) next(rw, r) }) } // WrapFunc converts a http.HandlerFunc into a negroni.Handler so it can be used as a Negroni // middleware. The next http.HandlerFunc is automatically called after the Handler // is executed. func WrapFunc(handlerFunc http.HandlerFunc) Handler { return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { handlerFunc(rw, r) next(rw, r) }) } // Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler. // Negroni middleware is evaluated in the order that they are added to the stack using // the Use and UseHandler methods. type Negroni struct { middleware middleware handlers []Handler } // New returns a new Negroni instance with no middleware preconfigured. func New(handlers ...Handler) *Negroni { return &Negroni{ handlers: handlers, middleware: build(handlers), } } // With returns a new Negroni instance that is a combination of the negroni // receiver's handlers and the provided handlers. func (n *Negroni) With(handlers ...Handler) *Negroni { return New( append(n.handlers, handlers...)..., ) } // Classic returns a new Negroni instance with the default middleware already // in the stack. // // Recovery - Panic Recovery Middleware // Logger - Request/Response Logging // Static - Static File Serving func Classic() *Negroni { return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public"))) } func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) { n.middleware.ServeHTTP(NewResponseWriter(rw), r) } // Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. func (n *Negroni) Use(handler Handler) { if handler == nil { panic("handler cannot be nil") } n.handlers = append(n.handlers, handler) n.middleware = build(n.handlers) } // UseFunc adds a Negroni-style handler function onto the middleware stack. func (n *Negroni) UseFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)) { n.Use(HandlerFunc(handlerFunc)) } // UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. func (n *Negroni) UseHandler(handler http.Handler) { n.Use(Wrap(handler)) } // UseHandlerFunc adds a http.HandlerFunc-style handler function onto the middleware stack. func (n *Negroni) UseHandlerFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request)) { n.UseHandler(http.HandlerFunc(handlerFunc)) } // Run is a convenience function that runs the negroni stack as an HTTP // server. The addr string, if provided, takes the same format as http.ListenAndServe. // If no address is provided but the PORT environment variable is set, the PORT value is used. // If neither is provided, the address' value will equal the DefaultAddress constant. func (n *Negroni) Run(addr ...string) { l := log.New(os.Stdout, "[negroni] ", 0) finalAddr := detectAddress(addr...) l.Printf("listening on %s", finalAddr) l.Fatal(http.ListenAndServe(finalAddr, n)) } func detectAddress(addr ...string) string { if len(addr) > 0 { return addr[0] } if port := os.Getenv("PORT"); port != "" { return ":" + port } return DefaultAddress } // Returns a list of all the handlers in the current Negroni middleware chain. func (n *Negroni) Handlers() []Handler { return n.handlers } func build(handlers []Handler) middleware { var next middleware if len(handlers) == 0 { return voidMiddleware() } else if len(handlers) > 1 { next = build(handlers[1:]) } else { next = voidMiddleware() } return middleware{handlers[0], &next} } func voidMiddleware() middleware { return middleware{ HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}), &middleware{}, } } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/recovery.go ================================================ package negroni import ( "fmt" "log" "net/http" "os" "runtime" "runtime/debug" "text/template" ) const ( panicText = "PANIC: %s\n%s" panicHTML = ` PANIC: {{.RecoveredPanic}}

    Negroni - PANIC

    {{.RequestDescription}}

    Runtime error: {{.RecoveredPanic}}
    {{ if .Stack }}

    Runtime Stack

    {{.StackAsString}}
    {{ end }} ` nilRequestMessage = "Request is nil" ) var panicHTMLTemplate = template.Must(template.New("PanicPage").Parse(panicHTML)) // PanicInformation contains all // elements for printing stack informations. type PanicInformation struct { RecoveredPanic interface{} Stack []byte Request *http.Request } // StackAsString returns a printable version of the stack func (p *PanicInformation) StackAsString() string { return string(p.Stack) } // RequestDescription returns a printable description of the url func (p *PanicInformation) RequestDescription() string { if p.Request == nil { return nilRequestMessage } var queryOutput string if p.Request.URL.RawQuery != "" { queryOutput = "?" + p.Request.URL.RawQuery } return fmt.Sprintf("%s %s%s", p.Request.Method, p.Request.URL.Path, queryOutput) } // PanicFormatter is an interface on object can implement // to be able to output the stack trace type PanicFormatter interface { // FormatPanicError output the stack for a given answer/response. // In case the the middleware should not output the stack trace, // the field `Stack` of the passed `PanicInformation` instance equals `[]byte{}`. FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) } // TextPanicFormatter output the stack // as simple text on os.Stdout. If no `Content-Type` is set, // it will output the data as `text/plain; charset=utf-8`. // Otherwise, the origin `Content-Type` is kept. type TextPanicFormatter struct{} func (t *TextPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) { if rw.Header().Get("Content-Type") == "" { rw.Header().Set("Content-Type", "text/plain; charset=utf-8") } fmt.Fprintf(rw, panicText, infos.RecoveredPanic, infos.Stack) } // HTMLPanicFormatter output the stack inside // an HTML page. This has been largely inspired by // https://github.com/go-martini/martini/pull/156/commits. type HTMLPanicFormatter struct{} func (t *HTMLPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) { if rw.Header().Get("Content-Type") == "" { rw.Header().Set("Content-Type", "text/html; charset=utf-8") } panicHTMLTemplate.Execute(rw, infos) } // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. type Recovery struct { Logger ALogger PrintStack bool PanicHandlerFunc func(*PanicInformation) StackAll bool StackSize int Formatter PanicFormatter // Deprecated: Use PanicHandlerFunc instead to receive panic // error with additional information (see PanicInformation) ErrorHandlerFunc func(interface{}) } // NewRecovery returns a new instance of Recovery func NewRecovery() *Recovery { return &Recovery{ Logger: log.New(os.Stdout, "[negroni] ", 0), PrintStack: true, StackAll: false, StackSize: 1024 * 8, Formatter: &TextPanicFormatter{}, } } func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { defer func() { if err := recover(); err != nil { rw.WriteHeader(http.StatusInternalServerError) stack := make([]byte, rec.StackSize) stack = stack[:runtime.Stack(stack, rec.StackAll)] infos := &PanicInformation{RecoveredPanic: err, Request: r} if rec.PrintStack { infos.Stack = stack } rec.Logger.Printf(panicText, err, stack) rec.Formatter.FormatPanicError(rw, r, infos) if rec.ErrorHandlerFunc != nil { func() { defer func() { if err := recover(); err != nil { rec.Logger.Printf("provided ErrorHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack()) rec.Logger.Printf("%s\n", debug.Stack()) } }() rec.ErrorHandlerFunc(err) }() } if rec.PanicHandlerFunc != nil { func() { defer func() { if err := recover(); err != nil { rec.Logger.Printf("provided PanicHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack()) rec.Logger.Printf("%s\n", debug.Stack()) } }() rec.PanicHandlerFunc(infos) }() } } }() next(rw, r) } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/response_writer.go ================================================ package negroni import ( "bufio" "fmt" "net" "net/http" ) // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter // if the functionality calls for it. type ResponseWriter interface { http.ResponseWriter http.Flusher // Status returns the status code of the response or 0 if the response has // not been written Status() int // Written returns whether or not the ResponseWriter has been written. Written() bool // Size returns the size of the response body. Size() int // Before allows for a function to be called before the ResponseWriter has been written to. This is // useful for setting headers or any other operations that must happen before a response has been written. Before(func(ResponseWriter)) } type beforeFunc func(ResponseWriter) // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { nrw := &responseWriter{ ResponseWriter: rw, } if _, ok := rw.(http.CloseNotifier); ok { return &responseWriterCloseNotifer{nrw} } return nrw } type responseWriter struct { http.ResponseWriter status int size int beforeFuncs []beforeFunc } func (rw *responseWriter) WriteHeader(s int) { rw.status = s rw.callBefore() rw.ResponseWriter.WriteHeader(s) } func (rw *responseWriter) Write(b []byte) (int, error) { if !rw.Written() { // The status will be StatusOK if WriteHeader has not been called yet rw.WriteHeader(http.StatusOK) } size, err := rw.ResponseWriter.Write(b) rw.size += size return size, err } func (rw *responseWriter) Status() int { return rw.status } func (rw *responseWriter) Size() int { return rw.size } func (rw *responseWriter) Written() bool { return rw.status != 0 } func (rw *responseWriter) Before(before func(ResponseWriter)) { rw.beforeFuncs = append(rw.beforeFuncs, before) } func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hijacker, ok := rw.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") } return hijacker.Hijack() } func (rw *responseWriter) callBefore() { for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { rw.beforeFuncs[i](rw) } } func (rw *responseWriter) Flush() { flusher, ok := rw.ResponseWriter.(http.Flusher) if ok { if !rw.Written() { // The status will be StatusOK if WriteHeader has not been called yet rw.WriteHeader(http.StatusOK) } flusher.Flush() } } type responseWriterCloseNotifer struct { *responseWriter } func (rw *responseWriterCloseNotifer) CloseNotify() <-chan bool { return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/response_writer_pusher.go ================================================ //go:build go1.8 // +build go1.8 package negroni import ( "fmt" "net/http" ) func (rw *responseWriter) Push(target string, opts *http.PushOptions) error { pusher, ok := rw.ResponseWriter.(http.Pusher) if ok { return pusher.Push(target, opts) } return fmt.Errorf("the ResponseWriter doesn't support the Pusher interface") } ================================================ FILE: examples/web/vendor/github.com/codegangsta/negroni/static.go ================================================ package negroni import ( "net/http" "path" "strings" ) // Static is a middleware handler that serves static files in the given // directory/filesystem. If the file does not exist on the filesystem, it // passes along to the next middleware in the chain. If you desire "fileserver" // type behavior where it returns a 404 for unfound files, you should consider // using http.FileServer from the Go stdlib. type Static struct { // Dir is the directory to serve static files from Dir http.FileSystem // Prefix is the optional prefix used to serve the static directory content Prefix string // IndexFile defines which file to serve as index if it exists. IndexFile string } // NewStatic returns a new instance of Static func NewStatic(directory http.FileSystem) *Static { return &Static{ Dir: directory, Prefix: "", IndexFile: "index.html", } } func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if r.Method != "GET" && r.Method != "HEAD" { next(rw, r) return } file := r.URL.Path // if we have a prefix, filter requests by stripping the prefix if s.Prefix != "" { if !strings.HasPrefix(file, s.Prefix) { next(rw, r) return } file = file[len(s.Prefix):] if file != "" && file[0] != '/' { next(rw, r) return } } f, err := s.Dir.Open(file) if err != nil { // discard the error? next(rw, r) return } defer f.Close() fi, err := f.Stat() if err != nil { next(rw, r) return } // try to serve index file if fi.IsDir() { // redirect if missing trailing slash if !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound) return } file = path.Join(file, s.IndexFile) f, err = s.Dir.Open(file) if err != nil { next(rw, r) return } defer f.Close() fi, err = f.Stat() if err != nil || fi.IsDir() { next(rw, r) return } } http.ServeContent(rw, r, file, fi.ModTime(), f) } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/commandinfo.go ================================================ // Copyright 2014 Gary Burd // // 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. package redis import ( "strings" ) const ( connectionWatchState = 1 << iota connectionMultiState connectionSubscribeState connectionMonitorState ) type commandInfo struct { // Set or Clear these states on connection. Set, Clear int } var commandInfos = map[string]commandInfo{ "WATCH": {Set: connectionWatchState}, "UNWATCH": {Clear: connectionWatchState}, "MULTI": {Set: connectionMultiState}, "EXEC": {Clear: connectionWatchState | connectionMultiState}, "DISCARD": {Clear: connectionWatchState | connectionMultiState}, "PSUBSCRIBE": {Set: connectionSubscribeState}, "SUBSCRIBE": {Set: connectionSubscribeState}, "MONITOR": {Set: connectionMonitorState}, } func init() { for n, ci := range commandInfos { commandInfos[strings.ToLower(n)] = ci } } func lookupCommandInfo(commandName string) commandInfo { if ci, ok := commandInfos[commandName]; ok { return ci } return commandInfos[strings.ToUpper(commandName)] } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/conn.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "bufio" "bytes" "context" "crypto/tls" "errors" "fmt" "io" "net" "net/url" "regexp" "strconv" "sync" "time" ) var ( _ ConnWithTimeout = (*conn)(nil) ) // conn is the low-level implementation of Conn type conn struct { // Shared mu sync.Mutex pending int err error conn net.Conn // Read readTimeout time.Duration br *bufio.Reader // Write writeTimeout time.Duration bw *bufio.Writer // Scratch space for formatting argument length. // '*' or '$', length, "\r\n" lenScratch [32]byte // Scratch space for formatting integers and floats. numScratch [40]byte } // DialTimeout acts like Dial but takes timeouts for establishing the // connection to the server, writing a command and reading a reply. // // Deprecated: Use Dial with options instead. func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { return Dial(network, address, DialConnectTimeout(connectTimeout), DialReadTimeout(readTimeout), DialWriteTimeout(writeTimeout)) } // DialOption specifies an option for dialing a Redis server. type DialOption struct { f func(*dialOptions) } type dialOptions struct { readTimeout time.Duration writeTimeout time.Duration tlsHandshakeTimeout time.Duration dialer *net.Dialer dialContext func(ctx context.Context, network, addr string) (net.Conn, error) db int username string password string clientName string useTLS bool skipVerify bool tlsConfig *tls.Config } // DialTLSHandshakeTimeout specifies the maximum amount of time waiting to // wait for a TLS handshake. Zero means no timeout. // If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds. func DialTLSHandshakeTimeout(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { do.tlsHandshakeTimeout = d }} } // DialReadTimeout specifies the timeout for reading a single command reply. func DialReadTimeout(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { do.readTimeout = d }} } // DialWriteTimeout specifies the timeout for writing a single command. func DialWriteTimeout(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { do.writeTimeout = d }} } // DialConnectTimeout specifies the timeout for connecting to the Redis server when // no DialNetDial option is specified. // If no DialConnectTimeout option is specified then the default is 30 seconds. func DialConnectTimeout(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { do.dialer.Timeout = d }} } // DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server // when no DialNetDial option is specified. // If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then // the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. func DialKeepAlive(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { do.dialer.KeepAlive = d }} } // DialNetDial specifies a custom dial function for creating TCP // connections, otherwise a net.Dialer customized via the other options is used. // DialNetDial overrides DialConnectTimeout and DialKeepAlive. func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { return DialOption{func(do *dialOptions) { do.dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return dial(network, addr) } }} } // DialContextFunc specifies a custom dial function with context for creating TCP // connections, otherwise a net.Dialer customized via the other options is used. // DialContextFunc overrides DialConnectTimeout and DialKeepAlive. func DialContextFunc(f func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption { return DialOption{func(do *dialOptions) { do.dialContext = f }} } // DialDatabase specifies the database to select when dialing a connection. func DialDatabase(db int) DialOption { return DialOption{func(do *dialOptions) { do.db = db }} } // DialPassword specifies the password to use when connecting to // the Redis server. func DialPassword(password string) DialOption { return DialOption{func(do *dialOptions) { do.password = password }} } // DialUsername specifies the username to use when connecting to // the Redis server when Redis ACLs are used. // A DialPassword must also be passed otherwise this option will have no effect. func DialUsername(username string) DialOption { return DialOption{func(do *dialOptions) { do.username = username }} } // DialClientName specifies a client name to be used // by the Redis server connection. func DialClientName(name string) DialOption { return DialOption{func(do *dialOptions) { do.clientName = name }} } // DialTLSConfig specifies the config to use when a TLS connection is dialed. // Has no effect when not dialing a TLS connection. func DialTLSConfig(c *tls.Config) DialOption { return DialOption{func(do *dialOptions) { do.tlsConfig = c }} } // DialTLSSkipVerify disables server name verification when connecting over // TLS. Has no effect when not dialing a TLS connection. func DialTLSSkipVerify(skip bool) DialOption { return DialOption{func(do *dialOptions) { do.skipVerify = skip }} } // DialUseTLS specifies whether TLS should be used when connecting to the // server. This option is ignore by DialURL. func DialUseTLS(useTLS bool) DialOption { return DialOption{func(do *dialOptions) { do.useTLS = useTLS }} } // Dial connects to the Redis server at the given network and // address using the specified options. func Dial(network, address string, options ...DialOption) (Conn, error) { return DialContext(context.Background(), network, address, options...) } type tlsHandshakeTimeoutError struct{} func (tlsHandshakeTimeoutError) Timeout() bool { return true } func (tlsHandshakeTimeoutError) Temporary() bool { return true } func (tlsHandshakeTimeoutError) Error() string { return "TLS handshake timeout" } // DialContext connects to the Redis server at the given network and // address using the specified options and context. func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) { do := dialOptions{ dialer: &net.Dialer{ Timeout: time.Second * 30, KeepAlive: time.Minute * 5, }, tlsHandshakeTimeout: time.Second * 10, } for _, option := range options { option.f(&do) } if do.dialContext == nil { do.dialContext = do.dialer.DialContext } netConn, err := do.dialContext(ctx, network, address) if err != nil { return nil, err } if do.useTLS { var tlsConfig *tls.Config if do.tlsConfig == nil { tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} } else { tlsConfig = do.tlsConfig.Clone() } if tlsConfig.ServerName == "" { host, _, err := net.SplitHostPort(address) if err != nil { netConn.Close() return nil, err } tlsConfig.ServerName = host } tlsConn := tls.Client(netConn, tlsConfig) errc := make(chan error, 2) // buffered so we don't block timeout or Handshake if d := do.tlsHandshakeTimeout; d != 0 { timer := time.AfterFunc(d, func() { errc <- tlsHandshakeTimeoutError{} }) defer timer.Stop() } go func() { errc <- tlsConn.Handshake() }() if err := <-errc; err != nil { // Timeout or Handshake error. netConn.Close() // nolint: errcheck return nil, err } netConn = tlsConn } c := &conn{ conn: netConn, bw: bufio.NewWriter(netConn), br: bufio.NewReader(netConn), readTimeout: do.readTimeout, writeTimeout: do.writeTimeout, } if do.password != "" { authArgs := make([]interface{}, 0, 2) if do.username != "" { authArgs = append(authArgs, do.username) } authArgs = append(authArgs, do.password) if _, err := c.DoContext(ctx, "AUTH", authArgs...); err != nil { netConn.Close() return nil, err } } if do.clientName != "" { if _, err := c.DoContext(ctx, "CLIENT", "SETNAME", do.clientName); err != nil { netConn.Close() return nil, err } } if do.db != 0 { if _, err := c.DoContext(ctx, "SELECT", do.db); err != nil { netConn.Close() return nil, err } } return c, nil } var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) // DialURL wraps DialURLContext using context.Background. func DialURL(rawurl string, options ...DialOption) (Conn, error) { ctx := context.Background() return DialURLContext(ctx, rawurl, options...) } // DialURLContext connects to a Redis server at the given URL using the Redis // URI scheme. URLs should follow the draft IANA specification for the // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). func DialURLContext(ctx context.Context, rawurl string, options ...DialOption) (Conn, error) { u, err := url.Parse(rawurl) if err != nil { return nil, err } if u.Scheme != "redis" && u.Scheme != "rediss" { return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) } if u.Opaque != "" { return nil, fmt.Errorf("invalid redis URL, url is opaque: %s", rawurl) } // As per the IANA draft spec, the host defaults to localhost and // the port defaults to 6379. host, port, err := net.SplitHostPort(u.Host) if err != nil { // assume port is missing host = u.Host port = "6379" } if host == "" { host = "localhost" } address := net.JoinHostPort(host, port) if u.User != nil { password, isSet := u.User.Password() username := u.User.Username() if isSet { if username != "" { // ACL options = append(options, DialUsername(username), DialPassword(password)) } else { // requirepass - user-info username:password with blank username options = append(options, DialPassword(password)) } } else if username != "" { // requirepass - redis-cli compatibility which treats as single arg in user-info as a password options = append(options, DialPassword(username)) } } match := pathDBRegexp.FindStringSubmatch(u.Path) if len(match) == 2 { db := 0 if len(match[1]) > 0 { db, err = strconv.Atoi(match[1]) if err != nil { return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) } } if db != 0 { options = append(options, DialDatabase(db)) } } else if u.Path != "" { return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) } options = append(options, DialUseTLS(u.Scheme == "rediss")) return DialContext(ctx, "tcp", address, options...) } // NewConn returns a new Redigo connection for the given net connection. func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { return &conn{ conn: netConn, bw: bufio.NewWriter(netConn), br: bufio.NewReader(netConn), readTimeout: readTimeout, writeTimeout: writeTimeout, } } func (c *conn) Close() error { c.mu.Lock() err := c.err if c.err == nil { c.err = errors.New("redigo: closed") err = c.conn.Close() } c.mu.Unlock() return err } func (c *conn) fatal(err error) error { c.mu.Lock() if c.err == nil { c.err = err // Close connection to force errors on subsequent calls and to unblock // other reader or writer. c.conn.Close() } c.mu.Unlock() return err } func (c *conn) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err } func (c *conn) writeLen(prefix byte, n int) error { c.lenScratch[len(c.lenScratch)-1] = '\n' c.lenScratch[len(c.lenScratch)-2] = '\r' i := len(c.lenScratch) - 3 for { c.lenScratch[i] = byte('0' + n%10) i -= 1 n = n / 10 if n == 0 { break } } c.lenScratch[i] = prefix _, err := c.bw.Write(c.lenScratch[i:]) return err } func (c *conn) writeString(s string) error { if err := c.writeLen('$', len(s)); err != nil { return err } if _, err := c.bw.WriteString(s); err != nil { return err } _, err := c.bw.WriteString("\r\n") return err } func (c *conn) writeBytes(p []byte) error { if err := c.writeLen('$', len(p)); err != nil { return err } if _, err := c.bw.Write(p); err != nil { return err } _, err := c.bw.WriteString("\r\n") return err } func (c *conn) writeInt64(n int64) error { return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) } func (c *conn) writeFloat64(n float64) error { return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) } func (c *conn) writeCommand(cmd string, args []interface{}) error { if err := c.writeLen('*', 1+len(args)); err != nil { return err } if err := c.writeString(cmd); err != nil { return err } for _, arg := range args { if err := c.writeArg(arg, true); err != nil { return err } } return nil } func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { switch arg := arg.(type) { case string: return c.writeString(arg) case []byte: return c.writeBytes(arg) case int: return c.writeInt64(int64(arg)) case int64: return c.writeInt64(arg) case float64: return c.writeFloat64(arg) case bool: if arg { return c.writeString("1") } else { return c.writeString("0") } case nil: return c.writeString("") case Argument: if argumentTypeOK { return c.writeArg(arg.RedisArg(), false) } // See comment in default clause below. var buf bytes.Buffer fmt.Fprint(&buf, arg) return c.writeBytes(buf.Bytes()) default: // This default clause is intended to handle builtin numeric types. // The function should return an error for other types, but this is not // done for compatibility with previous versions of the package. var buf bytes.Buffer fmt.Fprint(&buf, arg) return c.writeBytes(buf.Bytes()) } } type protocolError string func (pe protocolError) Error() string { return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) } // readLine reads a line of input from the RESP stream. func (c *conn) readLine() ([]byte, error) { // To avoid allocations, attempt to read the line using ReadSlice. This // call typically succeeds. The known case where the call fails is when // reading the output from the MONITOR command. p, err := c.br.ReadSlice('\n') if err == bufio.ErrBufferFull { // The line does not fit in the bufio.Reader's buffer. Fall back to // allocating a buffer for the line. buf := append([]byte{}, p...) for err == bufio.ErrBufferFull { p, err = c.br.ReadSlice('\n') buf = append(buf, p...) } p = buf } if err != nil { return nil, err } i := len(p) - 2 if i < 0 || p[i] != '\r' { return nil, protocolError("bad response line terminator") } return p[:i], nil } // parseLen parses bulk string and array lengths. func parseLen(p []byte) (int, error) { if len(p) == 0 { return -1, protocolError("malformed length") } if p[0] == '-' && len(p) == 2 && p[1] == '1' { // handle $-1 and $-1 null replies. return -1, nil } var n int for _, b := range p { n *= 10 if b < '0' || b > '9' { return -1, protocolError("illegal bytes in length") } n += int(b - '0') } return n, nil } // parseInt parses an integer reply. func parseInt(p []byte) (interface{}, error) { if len(p) == 0 { return 0, protocolError("malformed integer") } var negate bool if p[0] == '-' { negate = true p = p[1:] if len(p) == 0 { return 0, protocolError("malformed integer") } } var n int64 for _, b := range p { n *= 10 if b < '0' || b > '9' { return 0, protocolError("illegal bytes in length") } n += int64(b - '0') } if negate { n = -n } return n, nil } var ( okReply interface{} = "OK" pongReply interface{} = "PONG" ) func (c *conn) readReply() (interface{}, error) { line, err := c.readLine() if err != nil { return nil, err } if len(line) == 0 { return nil, protocolError("short response line") } switch line[0] { case '+': switch string(line[1:]) { case "OK": // Avoid allocation for frequent "+OK" response. return okReply, nil case "PONG": // Avoid allocation in PING command benchmarks :) return pongReply, nil default: return string(line[1:]), nil } case '-': return Error(line[1:]), nil case ':': return parseInt(line[1:]) case '$': n, err := parseLen(line[1:]) if n < 0 || err != nil { return nil, err } p := make([]byte, n) _, err = io.ReadFull(c.br, p) if err != nil { return nil, err } if line, err := c.readLine(); err != nil { return nil, err } else if len(line) != 0 { return nil, protocolError("bad bulk string format") } return p, nil case '*': n, err := parseLen(line[1:]) if n < 0 || err != nil { return nil, err } r := make([]interface{}, n) for i := range r { r[i], err = c.readReply() if err != nil { return nil, err } } return r, nil } return nil, protocolError("unexpected response line") } func (c *conn) Send(cmd string, args ...interface{}) error { c.mu.Lock() c.pending += 1 c.mu.Unlock() if c.writeTimeout != 0 { if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { return c.fatal(err) } } if err := c.writeCommand(cmd, args); err != nil { return c.fatal(err) } return nil } func (c *conn) Flush() error { if c.writeTimeout != 0 { if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { return c.fatal(err) } } if err := c.bw.Flush(); err != nil { return c.fatal(err) } return nil } func (c *conn) Receive() (interface{}, error) { return c.ReceiveWithTimeout(c.readTimeout) } func (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) { var realTimeout time.Duration if dl, ok := ctx.Deadline(); ok { timeout := time.Until(dl) if timeout >= c.readTimeout && c.readTimeout != 0 { realTimeout = c.readTimeout } else if timeout <= 0 { return nil, c.fatal(context.DeadlineExceeded) } else { realTimeout = timeout } } else { realTimeout = c.readTimeout } endch := make(chan struct{}) var r interface{} var e error go func() { defer close(endch) r, e = c.ReceiveWithTimeout(realTimeout) }() select { case <-ctx.Done(): return nil, c.fatal(ctx.Err()) case <-endch: return r, e } } func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { var deadline time.Time if timeout != 0 { deadline = time.Now().Add(timeout) } if err := c.conn.SetReadDeadline(deadline); err != nil { return nil, c.fatal(err) } if reply, err = c.readReply(); err != nil { return nil, c.fatal(err) } // When using pub/sub, the number of receives can be greater than the // number of sends. To enable normal use of the connection after // unsubscribing from all channels, we do not decrement pending to a // negative value. // // The pending field is decremented after the reply is read to handle the // case where Receive is called before Send. c.mu.Lock() if c.pending > 0 { c.pending -= 1 } c.mu.Unlock() if err, ok := reply.(Error); ok { return nil, err } return } func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { return c.DoWithTimeout(c.readTimeout, cmd, args...) } func (c *conn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { var realTimeout time.Duration if dl, ok := ctx.Deadline(); ok { timeout := time.Until(dl) if timeout >= c.readTimeout && c.readTimeout != 0 { realTimeout = c.readTimeout } else if timeout <= 0 { return nil, c.fatal(context.DeadlineExceeded) } else { realTimeout = timeout } } else { realTimeout = c.readTimeout } endch := make(chan struct{}) var r interface{} var e error go func() { defer close(endch) r, e = c.DoWithTimeout(realTimeout, cmd, args...) }() select { case <-ctx.Done(): return nil, c.fatal(ctx.Err()) case <-endch: return r, e } } func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { c.mu.Lock() pending := c.pending c.pending = 0 c.mu.Unlock() if cmd == "" && pending == 0 { return nil, nil } if c.writeTimeout != 0 { if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { return nil, c.fatal(err) } } if cmd != "" { if err := c.writeCommand(cmd, args); err != nil { return nil, c.fatal(err) } } if err := c.bw.Flush(); err != nil { return nil, c.fatal(err) } var deadline time.Time if readTimeout != 0 { deadline = time.Now().Add(readTimeout) } if err := c.conn.SetReadDeadline(deadline); err != nil { return nil, c.fatal(err) } if cmd == "" { reply := make([]interface{}, pending) for i := range reply { r, e := c.readReply() if e != nil { return nil, c.fatal(e) } reply[i] = r } return reply, nil } var err error var reply interface{} for i := 0; i <= pending; i++ { var e error if reply, e = c.readReply(); e != nil { return nil, c.fatal(e) } if e, ok := reply.(Error); ok && err == nil { err = e } } return reply, err } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/doc.go ================================================ // Copyright 2012 Gary Burd // // 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. // Package redis is a client for the Redis database. // // The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more // documentation about this package. // // # Connections // // The Conn interface is the primary interface for working with Redis. // Applications create connections by calling the Dial, DialWithTimeout or // NewConn functions. In the future, functions will be added for creating // sharded and other types of connections. // // The application must call the connection Close method when the application // is done with the connection. // // # Executing Commands // // The Conn interface has a generic method for executing Redis commands: // // Do(commandName string, args ...interface{}) (reply interface{}, err error) // // The Redis command reference (http://redis.io/commands) lists the available // commands. An example of using the Redis APPEND command is: // // n, err := conn.Do("APPEND", "key", "value") // // The Do method converts command arguments to bulk strings for transmission // to the server as follows: // // Go Type Conversion // []byte Sent as is // string Sent as is // int, int64 strconv.FormatInt(v) // float64 strconv.FormatFloat(v, 'g', -1, 64) // bool true -> "1", false -> "0" // nil "" // all other types fmt.Fprint(w, v) // // Redis command reply types are represented using the following Go types: // // Redis type Go type // error redis.Error // integer int64 // simple string string // bulk string []byte or nil if value not present. // array []interface{} or nil if value not present. // // Use type assertions or the reply helper functions to convert from // interface{} to the specific Go type for the command result. // // # Pipelining // // Connections support pipelining using the Send, Flush and Receive methods. // // Send(commandName string, args ...interface{}) error // Flush() error // Receive() (reply interface{}, err error) // // Send writes the command to the connection's output buffer. Flush flushes the // connection's output buffer to the server. Receive reads a single reply from // the server. The following example shows a simple pipeline. // // c.Send("SET", "foo", "bar") // c.Send("GET", "foo") // c.Flush() // c.Receive() // reply from SET // v, err = c.Receive() // reply from GET // // The Do method combines the functionality of the Send, Flush and Receive // methods. The Do method starts by writing the command and flushing the output // buffer. Next, the Do method receives all pending replies including the reply // for the command just sent by Do. If any of the received replies is an error, // then Do returns the error. If there are no errors, then Do returns the last // reply. If the command argument to the Do method is "", then the Do method // will flush the output buffer and receive pending replies without sending a // command. // // Use the Send and Do methods to implement pipelined transactions. // // c.Send("MULTI") // c.Send("INCR", "foo") // c.Send("INCR", "bar") // r, err := c.Do("EXEC") // fmt.Println(r) // prints [1, 1] // // # Concurrency // // Connections support one concurrent caller to the Receive method and one // concurrent caller to the Send and Flush methods. No other concurrency is // supported including concurrent calls to the Do and Close methods. // // For full concurrent access to Redis, use the thread-safe Pool to get, use // and release a connection from within a goroutine. Connections returned from // a Pool have the concurrency restrictions described in the previous // paragraph. // // # Publish and Subscribe // // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. // // c.Send("SUBSCRIBE", "example") // c.Flush() // for { // reply, err := c.Receive() // if err != nil { // return err // } // // process pushed message // } // // The PubSubConn type wraps a Conn with convenience methods for implementing // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods // send and flush a subscription management command. The receive method // converts a pushed message to convenient types for use in a type switch. // // psc := redis.PubSubConn{Conn: c} // psc.Subscribe("example") // for { // switch v := psc.Receive().(type) { // case redis.Message: // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) // case redis.Subscription: // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) // case error: // return v // } // } // // # Reply Helpers // // The Bool, Int, Bytes, String, Strings and Values functions convert a reply // to a value of a specific type. To allow convenient wrapping of calls to the // connection Do and Receive methods, the functions take a second argument of // type error. If the error is non-nil, then the helper function returns the // error. If the error is nil, the function converts the reply to the specified // type: // // exists, err := redis.Bool(c.Do("EXISTS", "foo")) // if err != nil { // // handle error return from c.Do or type conversion error. // } // // The Scan function converts elements of a array reply to Go types: // // var value1 int // var value2 string // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) // if err != nil { // // handle error // } // if _, err := redis.Scan(reply, &value1, &value2); err != nil { // // handle error // } // // # Errors // // Connection methods return error replies from the server as type redis.Error. // // Call the connection Err() method to determine if the connection encountered // non-recoverable error such as a network error or protocol parsing error. If // Err() returns a non-nil value, then the connection is not usable and should // be closed. package redis ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/log.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "bytes" "context" "fmt" "log" "time" ) var ( _ ConnWithTimeout = (*loggingConn)(nil) ) // NewLoggingConn returns a logging wrapper around a connection. func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { if prefix != "" { prefix = prefix + "." } return &loggingConn{conn, logger, prefix, nil} } // NewLoggingConnFilter returns a logging wrapper around a connection and a filter function. func NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) Conn { if prefix != "" { prefix = prefix + "." } return &loggingConn{conn, logger, prefix, skip} } type loggingConn struct { Conn logger *log.Logger prefix string skip func(cmdName string) bool } func (c *loggingConn) Close() error { err := c.Conn.Close() var buf bytes.Buffer fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) c.logger.Output(2, buf.String()) // nolint: errcheck return err } func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { const chop = 32 switch v := v.(type) { case []byte: if len(v) > chop { fmt.Fprintf(buf, "%q...", v[:chop]) } else { fmt.Fprintf(buf, "%q", v) } case string: if len(v) > chop { fmt.Fprintf(buf, "%q...", v[:chop]) } else { fmt.Fprintf(buf, "%q", v) } case []interface{}: if len(v) == 0 { buf.WriteString("[]") } else { sep := "[" fin := "]" if len(v) > chop { v = v[:chop] fin = "...]" } for _, vv := range v { buf.WriteString(sep) c.printValue(buf, vv) sep = ", " } buf.WriteString(fin) } default: fmt.Fprint(buf, v) } } func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { if c.skip != nil && c.skip(commandName) { return } var buf bytes.Buffer fmt.Fprintf(&buf, "%s%s(", c.prefix, method) if method != "Receive" { buf.WriteString(commandName) for _, arg := range args { buf.WriteString(", ") c.printValue(&buf, arg) } } buf.WriteString(") -> (") if method != "Send" { c.printValue(&buf, reply) buf.WriteString(", ") } fmt.Fprintf(&buf, "%v)", err) c.logger.Output(3, buf.String()) // nolint: errcheck } func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { reply, err := c.Conn.Do(commandName, args...) c.print("Do", commandName, args, reply, err) return reply, err } func (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) { reply, err := DoContext(c.Conn, ctx, commandName, args...) c.print("DoContext", commandName, args, reply, err) return reply, err } func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) c.print("DoWithTimeout", commandName, args, reply, err) return reply, err } func (c *loggingConn) Send(commandName string, args ...interface{}) error { err := c.Conn.Send(commandName, args...) c.print("Send", commandName, args, nil, err) return err } func (c *loggingConn) Receive() (interface{}, error) { reply, err := c.Conn.Receive() c.print("Receive", "", nil, reply, err) return reply, err } func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) { reply, err := ReceiveContext(c.Conn, ctx) c.print("ReceiveContext", "", nil, reply, err) return reply, err } func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { reply, err := ReceiveWithTimeout(c.Conn, timeout) c.print("ReceiveWithTimeout", "", nil, reply, err) return reply, err } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/pool.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "bytes" "context" "crypto/rand" "crypto/sha1" "errors" "io" "strconv" "sync" "time" ) var ( _ ConnWithTimeout = (*activeConn)(nil) _ ConnWithTimeout = (*errorConn)(nil) ) var nowFunc = time.Now // for testing // ErrPoolExhausted is returned from a pool connection method (Do, Send, // Receive, Flush, Err) when the maximum number of database connections in the // pool has been reached. var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") var ( errConnClosed = errors.New("redigo: connection closed") ) // Pool maintains a pool of connections. The application calls the Get method // to get a connection from the pool and the connection's Close method to // return the connection's resources to the pool. // // The following example shows how to use a pool in a web application. The // application creates a pool at application startup and makes it available to // request handlers using a package level variable. The pool configuration used // here is an example, not a recommendation. // // func newPool(addr string) *redis.Pool { // return &redis.Pool{ // MaxIdle: 3, // IdleTimeout: 240 * time.Second, // // Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial. // Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, // } // } // // var ( // pool *redis.Pool // redisServer = flag.String("redisServer", ":6379", "") // ) // // func main() { // flag.Parse() // pool = newPool(*redisServer) // ... // } // // A request handler gets a connection from the pool and closes the connection // when the handler is done: // // func serveHome(w http.ResponseWriter, r *http.Request) { // conn := pool.Get() // defer conn.Close() // ... // } // // Use the Dial function to authenticate connections with the AUTH command or // select a database with the SELECT command: // // pool := &redis.Pool{ // // Other pool configuration not shown in this example. // Dial: func () (redis.Conn, error) { // c, err := redis.Dial("tcp", server) // if err != nil { // return nil, err // } // if _, err := c.Do("AUTH", password); err != nil { // c.Close() // return nil, err // } // if _, err := c.Do("SELECT", db); err != nil { // c.Close() // return nil, err // } // return c, nil // }, // } // // Use the TestOnBorrow function to check the health of an idle connection // before the connection is returned to the application. This example PINGs // connections that have been idle more than a minute: // // pool := &redis.Pool{ // // Other pool configuration not shown in this example. // TestOnBorrow: func(c redis.Conn, t time.Time) error { // if time.Since(t) < time.Minute { // return nil // } // _, err := c.Do("PING") // return err // }, // } type Pool struct { // Dial is an application supplied function for creating and configuring a // connection. // // The connection returned from Dial must not be in a special state // (subscribed to pubsub channel, transaction started, ...). Dial func() (Conn, error) // DialContext is an application supplied function for creating and configuring a // connection with the given context. // // The connection returned from Dial must not be in a special state // (subscribed to pubsub channel, transaction started, ...). DialContext func(ctx context.Context) (Conn, error) // TestOnBorrow is an optional application supplied function for checking // the health of an idle connection before the connection is used again by // the application. Argument t is the time that the connection was returned // to the pool. If the function returns an error, then the connection is // closed. TestOnBorrow func(c Conn, t time.Time) error // Maximum number of idle connections in the pool. MaxIdle int // Maximum number of connections allocated by the pool at a given time. // When zero, there is no limit on the number of connections in the pool. MaxActive int // Close connections after remaining idle for this duration. If the value // is zero, then idle connections are not closed. Applications should set // the timeout to a value less than the server's timeout. IdleTimeout time.Duration // If Wait is true and the pool is at the MaxActive limit, then Get() waits // for a connection to be returned to the pool before returning. Wait bool // Close connections older than this duration. If the value is zero, then // the pool does not close connections based on age. MaxConnLifetime time.Duration mu sync.Mutex // mu protects the following fields closed bool // set to true when the pool is closed. active int // the number of open connections in the pool initOnce sync.Once // the init ch once func ch chan struct{} // limits open connections when p.Wait is true idle idleList // idle connections waitCount int64 // total number of connections waited for. waitDuration time.Duration // total time waited for new connections. } // NewPool creates a new pool. // // Deprecated: Initialize the Pool directly as shown in the example. func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { return &Pool{Dial: newFn, MaxIdle: maxIdle} } // Get gets a connection. The application must close the returned connection. // This method always returns a valid connection so that applications can defer // error handling to the first use of the connection. If there is an error // getting an underlying connection, then the connection Err, Do, Send, Flush // and Receive methods return that error. func (p *Pool) Get() Conn { // GetContext returns errorConn in the first argument when an error occurs. c, _ := p.GetContext(context.Background()) return c } // GetContext gets a connection using the provided context. // // The provided Context must be non-nil. If the context expires before the // connection is complete, an error is returned. Any expiration on the context // will not affect the returned connection. // // If the function completes without error, then the application must close the // returned connection. func (p *Pool) GetContext(ctx context.Context) (Conn, error) { // Wait until there is a vacant connection in the pool. waited, err := p.waitVacantConn(ctx) if err != nil { return errorConn{err}, err } p.mu.Lock() if waited > 0 { p.waitCount++ p.waitDuration += waited } // Prune stale connections at the back of the idle list. if p.IdleTimeout > 0 { n := p.idle.count for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { pc := p.idle.back p.idle.popBack() p.mu.Unlock() pc.c.Close() p.mu.Lock() p.active-- } } // Get idle connection from the front of idle list. for p.idle.front != nil { pc := p.idle.front p.idle.popFront() p.mu.Unlock() if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { return &activeConn{p: p, pc: pc}, nil } pc.c.Close() p.mu.Lock() p.active-- } // Check for pool closed before dialing a new connection. if p.closed { p.mu.Unlock() err := errors.New("redigo: get on closed pool") return errorConn{err}, err } // Handle limit for p.Wait == false. if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { p.mu.Unlock() return errorConn{ErrPoolExhausted}, ErrPoolExhausted } p.active++ p.mu.Unlock() c, err := p.dial(ctx) if err != nil { p.mu.Lock() p.active-- if p.ch != nil && !p.closed { p.ch <- struct{}{} } p.mu.Unlock() return errorConn{err}, err } return &activeConn{p: p, pc: &poolConn{c: c, created: nowFunc()}}, nil } // PoolStats contains pool statistics. type PoolStats struct { // ActiveCount is the number of connections in the pool. The count includes // idle connections and connections in use. ActiveCount int // IdleCount is the number of idle connections in the pool. IdleCount int // WaitCount is the total number of connections waited for. // This value is currently not guaranteed to be 100% accurate. WaitCount int64 // WaitDuration is the total time blocked waiting for a new connection. // This value is currently not guaranteed to be 100% accurate. WaitDuration time.Duration } // Stats returns pool's statistics. func (p *Pool) Stats() PoolStats { p.mu.Lock() stats := PoolStats{ ActiveCount: p.active, IdleCount: p.idle.count, WaitCount: p.waitCount, WaitDuration: p.waitDuration, } p.mu.Unlock() return stats } // ActiveCount returns the number of connections in the pool. The count // includes idle connections and connections in use. func (p *Pool) ActiveCount() int { p.mu.Lock() active := p.active p.mu.Unlock() return active } // IdleCount returns the number of idle connections in the pool. func (p *Pool) IdleCount() int { p.mu.Lock() idle := p.idle.count p.mu.Unlock() return idle } // Close releases the resources used by the pool. func (p *Pool) Close() error { p.mu.Lock() if p.closed { p.mu.Unlock() return nil } p.closed = true p.active -= p.idle.count pc := p.idle.front p.idle.count = 0 p.idle.front, p.idle.back = nil, nil if p.ch != nil { close(p.ch) } p.mu.Unlock() for ; pc != nil; pc = pc.next { pc.c.Close() } return nil } func (p *Pool) lazyInit() { p.initOnce.Do(func() { p.ch = make(chan struct{}, p.MaxActive) if p.closed { close(p.ch) } else { for i := 0; i < p.MaxActive; i++ { p.ch <- struct{}{} } } }) } // waitVacantConn waits for a vacant connection in pool if waiting // is enabled and pool size is limited, otherwise returns instantly. // If ctx expires before that, an error is returned. // // If there were no vacant connection in the pool right away it returns the time spent waiting // for that connection to appear in the pool. func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) { if !p.Wait || p.MaxActive <= 0 { // No wait or no connection limit. return 0, nil } p.lazyInit() // wait indicates if we believe it will block so its not 100% accurate // however for stats it should be good enough. wait := len(p.ch) == 0 var start time.Time if wait { start = time.Now() } select { case <-p.ch: // Additionally check that context hasn't expired while we were waiting, // because `select` picks a random `case` if several of them are "ready". select { case <-ctx.Done(): p.ch <- struct{}{} return 0, ctx.Err() default: } case <-ctx.Done(): return 0, ctx.Err() } if wait { return time.Since(start), nil } return 0, nil } func (p *Pool) dial(ctx context.Context) (Conn, error) { if p.DialContext != nil { return p.DialContext(ctx) } if p.Dial != nil { return p.Dial() } return nil, errors.New("redigo: must pass Dial or DialContext to pool") } func (p *Pool) put(pc *poolConn, forceClose bool) error { p.mu.Lock() if !p.closed && !forceClose { pc.t = nowFunc() p.idle.pushFront(pc) if p.idle.count > p.MaxIdle { pc = p.idle.back p.idle.popBack() } else { pc = nil } } if pc != nil { p.mu.Unlock() pc.c.Close() p.mu.Lock() p.active-- } if p.ch != nil && !p.closed { p.ch <- struct{}{} } p.mu.Unlock() return nil } type activeConn struct { p *Pool pc *poolConn state int } var ( sentinel []byte sentinelOnce sync.Once ) func initSentinel() { p := make([]byte, 64) if _, err := rand.Read(p); err == nil { sentinel = p } else { h := sha1.New() io.WriteString(h, "Oops, rand failed. Use time instead.") // nolint: errcheck io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) // nolint: errcheck sentinel = h.Sum(nil) } } func (ac *activeConn) firstError(errs ...error) error { for _, err := range errs[:len(errs)-1] { if err != nil { return err } } return errs[len(errs)-1] } func (ac *activeConn) Close() (err error) { pc := ac.pc if pc == nil { return nil } ac.pc = nil if ac.state&connectionMultiState != 0 { err = pc.c.Send("DISCARD") ac.state &^= (connectionMultiState | connectionWatchState) } else if ac.state&connectionWatchState != 0 { err = pc.c.Send("UNWATCH") ac.state &^= connectionWatchState } if ac.state&connectionSubscribeState != 0 { err = ac.firstError(err, pc.c.Send("UNSUBSCRIBE"), pc.c.Send("PUNSUBSCRIBE"), ) // To detect the end of the message stream, ask the server to echo // a sentinel value and read until we see that value. sentinelOnce.Do(initSentinel) err = ac.firstError(err, pc.c.Send("ECHO", sentinel), pc.c.Flush(), ) for { p, err2 := pc.c.Receive() if err2 != nil { err = ac.firstError(err, err2) break } if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { ac.state &^= connectionSubscribeState break } } } _, err2 := pc.c.Do("") return ac.firstError( err, err2, ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil), ) } func (ac *activeConn) Err() error { pc := ac.pc if pc == nil { return errConnClosed } return pc.c.Err() } func (ac *activeConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } cwt, ok := pc.c.(ConnWithContext) if !ok { return nil, errContextNotSupported } ci := lookupCommandInfo(commandName) ac.state = (ac.state | ci.Set) &^ ci.Clear return cwt.DoContext(ctx, commandName, args...) } func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } ci := lookupCommandInfo(commandName) ac.state = (ac.state | ci.Set) &^ ci.Clear return pc.c.Do(commandName, args...) } func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } cwt, ok := pc.c.(ConnWithTimeout) if !ok { return nil, errTimeoutNotSupported } ci := lookupCommandInfo(commandName) ac.state = (ac.state | ci.Set) &^ ci.Clear return cwt.DoWithTimeout(timeout, commandName, args...) } func (ac *activeConn) Send(commandName string, args ...interface{}) error { pc := ac.pc if pc == nil { return errConnClosed } ci := lookupCommandInfo(commandName) ac.state = (ac.state | ci.Set) &^ ci.Clear return pc.c.Send(commandName, args...) } func (ac *activeConn) Flush() error { pc := ac.pc if pc == nil { return errConnClosed } return pc.c.Flush() } func (ac *activeConn) Receive() (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } return pc.c.Receive() } func (ac *activeConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } cwt, ok := pc.c.(ConnWithContext) if !ok { return nil, errContextNotSupported } return cwt.ReceiveContext(ctx) } func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { pc := ac.pc if pc == nil { return nil, errConnClosed } cwt, ok := pc.c.(ConnWithTimeout) if !ok { return nil, errTimeoutNotSupported } return cwt.ReceiveWithTimeout(timeout) } type errorConn struct{ err error } func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } func (ec errorConn) DoContext(context.Context, string, ...interface{}) (interface{}, error) { return nil, ec.err } func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { return nil, ec.err } func (ec errorConn) Send(string, ...interface{}) error { return ec.err } func (ec errorConn) Err() error { return ec.err } func (ec errorConn) Close() error { return nil } func (ec errorConn) Flush() error { return ec.err } func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } func (ec errorConn) ReceiveContext(context.Context) (interface{}, error) { return nil, ec.err } func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } type idleList struct { count int front, back *poolConn } type poolConn struct { c Conn t time.Time created time.Time next, prev *poolConn } func (l *idleList) pushFront(pc *poolConn) { pc.next = l.front pc.prev = nil if l.count == 0 { l.back = pc } else { l.front.prev = pc } l.front = pc l.count++ } func (l *idleList) popFront() { pc := l.front l.count-- if l.count == 0 { l.front, l.back = nil, nil } else { pc.next.prev = nil l.front = pc.next } pc.next, pc.prev = nil, nil } func (l *idleList) popBack() { pc := l.back l.count-- if l.count == 0 { l.front, l.back = nil, nil } else { pc.prev.next = nil l.back = pc.prev } pc.next, pc.prev = nil, nil } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/pubsub.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "context" "errors" "time" ) // Subscription represents a subscribe or unsubscribe notification. type Subscription struct { // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" Kind string // The channel that was changed. Channel string // The current number of subscriptions for connection. Count int } // Message represents a message notification. type Message struct { // The originating channel. Channel string // The matched pattern, if any Pattern string // The message data. Data []byte } // Pong represents a pubsub pong notification. type Pong struct { Data string } // PubSubConn wraps a Conn with convenience methods for subscribers. type PubSubConn struct { Conn Conn } // Close closes the connection. func (c PubSubConn) Close() error { return c.Conn.Close() } // Subscribe subscribes the connection to the specified channels. func (c PubSubConn) Subscribe(channel ...interface{}) error { if err := c.Conn.Send("SUBSCRIBE", channel...); err != nil { return err } return c.Conn.Flush() } // PSubscribe subscribes the connection to the given patterns. func (c PubSubConn) PSubscribe(channel ...interface{}) error { if err := c.Conn.Send("PSUBSCRIBE", channel...); err != nil { return err } return c.Conn.Flush() } // Unsubscribe unsubscribes the connection from the given channels, or from all // of them if none is given. func (c PubSubConn) Unsubscribe(channel ...interface{}) error { if err := c.Conn.Send("UNSUBSCRIBE", channel...); err != nil { return err } return c.Conn.Flush() } // PUnsubscribe unsubscribes the connection from the given patterns, or from all // of them if none is given. func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { if err := c.Conn.Send("PUNSUBSCRIBE", channel...); err != nil { return err } return c.Conn.Flush() } // Ping sends a PING to the server with the specified data. // // The connection must be subscribed to at least one channel or pattern when // calling this method. func (c PubSubConn) Ping(data string) error { if err := c.Conn.Send("PING", data); err != nil { return err } return c.Conn.Flush() } // Receive returns a pushed message as a Subscription, Message, Pong or error. // The return value is intended to be used directly in a type switch as // illustrated in the PubSubConn example. func (c PubSubConn) Receive() interface{} { return c.receiveInternal(c.Conn.Receive()) } // ReceiveWithTimeout is like Receive, but it allows the application to // override the connection's default timeout. func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) } // ReceiveContext is like Receive, but it allows termination of the receive // via a Context. If the call returns due to closure of the context's Done // channel the underlying Conn will have been closed. func (c PubSubConn) ReceiveContext(ctx context.Context) interface{} { return c.receiveInternal(ReceiveContext(c.Conn, ctx)) } func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { reply, err := Values(replyArg, errArg) if err != nil { return err } var kind string reply, err = Scan(reply, &kind) if err != nil { return err } switch kind { case "message": var m Message if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { return err } return m case "pmessage": var m Message if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { return err } return m case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": s := Subscription{Kind: kind} if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { return err } return s case "pong": var p Pong if _, err := Scan(reply, &p.Data); err != nil { return err } return p } return errors.New("redigo: unknown pubsub notification") } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/redis.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "context" "errors" "time" ) // Error represents an error returned in a command reply. type Error string func (err Error) Error() string { return string(err) } // Conn represents a connection to a Redis server. type Conn interface { // Close closes the connection. Close() error // Err returns a non-nil value when the connection is not usable. Err() error // Do sends a command to the server and returns the received reply. // This function will use the timeout which was set when the connection is created Do(commandName string, args ...interface{}) (reply interface{}, err error) // Send writes the command to the client's output buffer. Send(commandName string, args ...interface{}) error // Flush flushes the output buffer to the Redis server. Flush() error // Receive receives a single reply from the Redis server Receive() (reply interface{}, err error) } // Argument is the interface implemented by an object which wants to control how // the object is converted to Redis bulk strings. type Argument interface { // RedisArg returns a value to be encoded as a bulk string per the // conversions listed in the section 'Executing Commands'. // Implementations should typically return a []byte or string. RedisArg() interface{} } // Scanner is implemented by an object which wants to control its value is // interpreted when read from Redis. type Scanner interface { // RedisScan assigns a value from a Redis value. The argument src is one of // the reply types listed in the section `Executing Commands`. // // An error should be returned if the value cannot be stored without // loss of information. RedisScan(src interface{}) error } // ConnWithTimeout is an optional interface that allows the caller to override // a connection's default read timeout. This interface is useful for executing // the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the // server. // // A connection's default read timeout is set with the DialReadTimeout dial // option. Applications should rely on the default timeout for commands that do // not block at the server. // // All of the Conn implementations in this package satisfy the ConnWithTimeout // interface. // // Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify // use of this interface. type ConnWithTimeout interface { Conn // DoWithTimeout sends a command to the server and returns the received reply. // The timeout overrides the readtimeout set when dialing the connection. DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) // ReceiveWithTimeout receives a single reply from the Redis server. // The timeout overrides the readtimeout set when dialing the connection. ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) } // ConnWithContext is an optional interface that allows the caller to control the command's life with context. type ConnWithContext interface { Conn // DoContext sends a command to server and returns the received reply. // min(ctx,DialReadTimeout()) will be used as the deadline. // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). // ctx timeout return err context.DeadlineExceeded. // ctx canceled return err context.Canceled. DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) // ReceiveContext receives a single reply from the Redis server. // min(ctx,DialReadTimeout()) will be used as the deadline. // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). // ctx timeout return err context.DeadlineExceeded. // ctx canceled return err context.Canceled. ReceiveContext(ctx context.Context) (reply interface{}, err error) } var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") var errContextNotSupported = errors.New("redis: connection does not support ConnWithContext") // DoContext sends a command to server and returns the received reply. // min(ctx,DialReadTimeout()) will be used as the deadline. // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). // ctx timeout return err context.DeadlineExceeded. // ctx canceled return err context.Canceled. func DoContext(c Conn, ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { cwt, ok := c.(ConnWithContext) if !ok { return nil, errContextNotSupported } return cwt.DoContext(ctx, cmd, args...) } // DoWithTimeout executes a Redis command with the specified read timeout. If // the connection does not satisfy the ConnWithTimeout interface, then an error // is returned. func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { cwt, ok := c.(ConnWithTimeout) if !ok { return nil, errTimeoutNotSupported } return cwt.DoWithTimeout(timeout, cmd, args...) } // ReceiveContext receives a single reply from the Redis server. // min(ctx,DialReadTimeout()) will be used as the deadline. // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. // DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), "io/timeout"). // ctx timeout return err context.DeadlineExceeded. // ctx canceled return err context.Canceled. func ReceiveContext(c Conn, ctx context.Context) (interface{}, error) { cwt, ok := c.(ConnWithContext) if !ok { return nil, errContextNotSupported } return cwt.ReceiveContext(ctx) } // ReceiveWithTimeout receives a reply with the specified read timeout. If the // connection does not satisfy the ConnWithTimeout interface, then an error is // returned. func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { cwt, ok := c.(ConnWithTimeout) if !ok { return nil, errTimeoutNotSupported } return cwt.ReceiveWithTimeout(timeout) } // SlowLog represents a redis SlowLog type SlowLog struct { // ID is a unique progressive identifier for every slow log entry. ID int64 // Time is the unix timestamp at which the logged command was processed. Time time.Time // ExecutationTime is the amount of time needed for the command execution. ExecutionTime time.Duration // Args is the command name and arguments Args []string // ClientAddr is the client IP address (4.0 only). ClientAddr string // ClientName is the name set via the CLIENT SETNAME command (4.0 only). ClientName string } // Latency represents a redis LATENCY LATEST. type Latency struct { // Name of the latest latency spike event. Name string // Time of the latest latency spike for the event. Time time.Time // Latest is the latest recorded latency for the named event. Latest time.Duration // Max is the maximum latency for the named event. Max time.Duration } // LatencyHistory represents a redis LATENCY HISTORY. type LatencyHistory struct { // Time is the unix timestamp at which the event was processed. Time time.Time // ExecutationTime is the amount of time needed for the command execution. ExecutionTime time.Duration } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/reflect.go ================================================ package redis import ( "reflect" "runtime" ) // methodName returns the name of the calling method, // assumed to be two stack frames above. func methodName() string { pc, _, _, _ := runtime.Caller(2) f := runtime.FuncForPC(pc) if f == nil { return "unknown method" } return f.Name() } // mustBe panics if f's kind is not expected. func mustBe(v reflect.Value, expected reflect.Kind) { if v.Kind() != expected { panic(&reflect.ValueError{Method: methodName(), Kind: v.Kind()}) } } // fieldByIndexCreate returns the nested field corresponding // to index creating elements that are nil when stepping through. // It panics if v is not a struct. func fieldByIndexCreate(v reflect.Value, index []int) reflect.Value { if len(index) == 1 { return v.Field(index[0]) } mustBe(v, reflect.Struct) for i, x := range index { if i > 0 { if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() } } v = v.Field(x) } return v } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/reflect_go117.go ================================================ //go:build !go1.18 // +build !go1.18 package redis import ( "errors" "reflect" ) // fieldByIndexErr returns the nested field corresponding to index. // It returns an error if evaluation requires stepping through a nil // pointer, but panics if it must step through a field that // is not a struct. func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { if len(index) == 1 { return v.Field(index[0]), nil } mustBe(v, reflect.Struct) for i, x := range index { if i > 0 { if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { if v.IsNil() { return reflect.Value{}, errors.New("reflect: indirection through nil pointer to embedded struct field " + v.Type().Elem().Name()) } v = v.Elem() } } v = v.Field(x) } return v, nil } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/reflect_go118.go ================================================ //go:build go1.18 // +build go1.18 package redis import ( "reflect" ) // fieldByIndexErr returns the nested field corresponding to index. // It returns an error if evaluation requires stepping through a nil // pointer, but panics if it must step through a field that // is not a struct. func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { return v.FieldByIndexErr(index) } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/reply.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "errors" "fmt" "strconv" "time" ) // ErrNil indicates that a reply value is nil. var ErrNil = errors.New("redigo: nil returned") // Int is a helper that converts a command reply to an integer. If err is not // equal to nil, then Int returns 0, err. Otherwise, Int converts the // reply to an int as follows: // // Reply type Result // integer int(reply), nil // bulk string parsed reply, nil // nil 0, ErrNil // other 0, error func Int(reply interface{}, err error) (int, error) { if err != nil { return 0, err } switch reply := reply.(type) { case int64: x := int(reply) if int64(x) != reply { return 0, strconv.ErrRange } return x, nil case []byte: n, err := strconv.ParseInt(string(reply), 10, 0) return int(n), err case nil: return 0, ErrNil case Error: return 0, reply } return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) } // Int64 is a helper that converts a command reply to 64 bit integer. If err is // not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the // reply to an int64 as follows: // // Reply type Result // integer reply, nil // bulk string parsed reply, nil // nil 0, ErrNil // other 0, error func Int64(reply interface{}, err error) (int64, error) { if err != nil { return 0, err } switch reply := reply.(type) { case int64: return reply, nil case []byte: n, err := strconv.ParseInt(string(reply), 10, 64) return n, err case nil: return 0, ErrNil case Error: return 0, reply } return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) } func errNegativeInt(v int64) error { return fmt.Errorf("redigo: unexpected negative value %v for Uint64", v) } // Uint64 is a helper that converts a command reply to 64 bit unsigned integer. // If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the // reply to an uint64 as follows: // // Reply type Result // +integer reply, nil // bulk string parsed reply, nil // nil 0, ErrNil // other 0, error func Uint64(reply interface{}, err error) (uint64, error) { if err != nil { return 0, err } switch reply := reply.(type) { case int64: if reply < 0 { return 0, errNegativeInt(reply) } return uint64(reply), nil case []byte: n, err := strconv.ParseUint(string(reply), 10, 64) return n, err case nil: return 0, ErrNil case Error: return 0, reply } return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) } // Float64 is a helper that converts a command reply to 64 bit float. If err is // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts // the reply to a float64 as follows: // // Reply type Result // bulk string parsed reply, nil // nil 0, ErrNil // other 0, error func Float64(reply interface{}, err error) (float64, error) { if err != nil { return 0, err } switch reply := reply.(type) { case []byte: n, err := strconv.ParseFloat(string(reply), 64) return n, err case nil: return 0, ErrNil case Error: return 0, reply } return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) } // String is a helper that converts a command reply to a string. If err is not // equal to nil, then String returns "", err. Otherwise String converts the // reply to a string as follows: // // Reply type Result // bulk string string(reply), nil // simple string reply, nil // nil "", ErrNil // other "", error func String(reply interface{}, err error) (string, error) { if err != nil { return "", err } switch reply := reply.(type) { case []byte: return string(reply), nil case string: return reply, nil case nil: return "", ErrNil case Error: return "", reply } return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) } // Bytes is a helper that converts a command reply to a slice of bytes. If err // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts // the reply to a slice of bytes as follows: // // Reply type Result // bulk string reply, nil // simple string []byte(reply), nil // nil nil, ErrNil // other nil, error func Bytes(reply interface{}, err error) ([]byte, error) { if err != nil { return nil, err } switch reply := reply.(type) { case []byte: return reply, nil case string: return []byte(reply), nil case nil: return nil, ErrNil case Error: return nil, reply } return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) } // Bool is a helper that converts a command reply to a boolean. If err is not // equal to nil, then Bool returns false, err. Otherwise Bool converts the // reply to boolean as follows: // // Reply type Result // integer value != 0, nil // bulk string strconv.ParseBool(reply) // nil false, ErrNil // other false, error func Bool(reply interface{}, err error) (bool, error) { if err != nil { return false, err } switch reply := reply.(type) { case int64: return reply != 0, nil case []byte: return strconv.ParseBool(string(reply)) case nil: return false, ErrNil case Error: return false, reply } return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) } // MultiBulk is a helper that converts an array command reply to a []interface{}. // // Deprecated: Use Values instead. func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } // Values is a helper that converts an array command reply to a []interface{}. // If err is not equal to nil, then Values returns nil, err. Otherwise, Values // converts the reply as follows: // // Reply type Result // array reply, nil // nil nil, ErrNil // other nil, error func Values(reply interface{}, err error) ([]interface{}, error) { if err != nil { return nil, err } switch reply := reply.(type) { case []interface{}: return reply, nil case nil: return nil, ErrNil case Error: return nil, reply } return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) } func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { if err != nil { return err } switch reply := reply.(type) { case []interface{}: makeSlice(len(reply)) for i := range reply { if reply[i] == nil { continue } if err := assign(i, reply[i]); err != nil { return err } } return nil case nil: return ErrNil case Error: return reply } return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) } // Float64s is a helper that converts an array command reply to a []float64. If // err is not equal to nil, then Float64s returns nil, err. Nil array items are // converted to 0 in the output slice. Floats64 returns an error if an array // item is not a bulk string or nil. func Float64s(reply interface{}, err error) ([]float64, error) { var result []float64 err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { switch v := v.(type) { case []byte: f, err := strconv.ParseFloat(string(v), 64) result[i] = f return err case Error: return v default: return fmt.Errorf("redigo: unexpected element type for Float64s, got type %T", v) } }) return result, err } // Strings is a helper that converts an array command reply to a []string. If // err is not equal to nil, then Strings returns nil, err. Nil array items are // converted to "" in the output slice. Strings returns an error if an array // item is not a bulk string or nil. func Strings(reply interface{}, err error) ([]string, error) { var result []string err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { switch v := v.(type) { case string: result[i] = v return nil case []byte: result[i] = string(v) return nil case Error: return v default: return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) } }) return result, err } // ByteSlices is a helper that converts an array command reply to a [][]byte. // If err is not equal to nil, then ByteSlices returns nil, err. Nil array // items are stay nil. ByteSlices returns an error if an array item is not a // bulk string or nil. func ByteSlices(reply interface{}, err error) ([][]byte, error) { var result [][]byte err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { switch v := v.(type) { case []byte: result[i] = v return nil case Error: return v default: return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) } }) return result, err } // Int64s is a helper that converts an array command reply to a []int64. // If err is not equal to nil, then Int64s returns nil, err. Nil array // items are stay nil. Int64s returns an error if an array item is not a // bulk string or nil. func Int64s(reply interface{}, err error) ([]int64, error) { var result []int64 err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { switch v := v.(type) { case int64: result[i] = v return nil case []byte: n, err := strconv.ParseInt(string(v), 10, 64) result[i] = n return err case Error: return v default: return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) } }) return result, err } // Ints is a helper that converts an array command reply to a []int. // If err is not equal to nil, then Ints returns nil, err. Nil array // items are stay nil. Ints returns an error if an array item is not a // bulk string or nil. func Ints(reply interface{}, err error) ([]int, error) { var result []int err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { switch v := v.(type) { case int64: n := int(v) if int64(n) != v { return strconv.ErrRange } result[i] = n return nil case []byte: n, err := strconv.Atoi(string(v)) result[i] = n return err case Error: return v default: return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) } }) return result, err } // mapHelper builds a map from the data in reply. func mapHelper(reply interface{}, err error, name string, makeMap func(int), assign func(key string, value interface{}) error) error { values, err := Values(reply, err) if err != nil { return err } if len(values)%2 != 0 { return fmt.Errorf("redigo: %s expects even number of values result, got %d", name, len(values)) } makeMap(len(values) / 2) for i := 0; i < len(values); i += 2 { key, ok := values[i].([]byte) if !ok { return fmt.Errorf("redigo: %s key[%d] not a bulk string value, got %T", name, i, values[i]) } if err := assign(string(key), values[i+1]); err != nil { return err } } return nil } // StringMap is a helper that converts an array of strings (alternating key, value) // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. // Requires an even number of values in result. func StringMap(reply interface{}, err error) (map[string]string, error) { var result map[string]string err = mapHelper(reply, err, "StringMap", func(n int) { result = make(map[string]string, n) }, func(key string, v interface{}) error { value, ok := v.([]byte) if !ok { return fmt.Errorf("redigo: StringMap for %q not a bulk string value, got %T", key, v) } result[key] = string(value) return nil }, ) return result, err } // IntMap is a helper that converts an array of strings (alternating key, value) // into a map[string]int. The HGETALL commands return replies in this format. // Requires an even number of values in result. func IntMap(result interface{}, err error) (map[string]int, error) { var m map[string]int err = mapHelper(result, err, "IntMap", func(n int) { m = make(map[string]int, n) }, func(key string, v interface{}) error { value, err := Int(v, nil) if err != nil { return err } m[key] = value return nil }, ) return m, err } // Int64Map is a helper that converts an array of strings (alternating key, value) // into a map[string]int64. The HGETALL commands return replies in this format. // Requires an even number of values in result. func Int64Map(result interface{}, err error) (map[string]int64, error) { var m map[string]int64 err = mapHelper(result, err, "Int64Map", func(n int) { m = make(map[string]int64, n) }, func(key string, v interface{}) error { value, err := Int64(v, nil) if err != nil { return err } m[key] = value return nil }, ) return m, err } // Float64Map is a helper that converts an array of strings (alternating key, value) // into a map[string]float64. The HGETALL commands return replies in this format. // Requires an even number of values in result. func Float64Map(result interface{}, err error) (map[string]float64, error) { var m map[string]float64 err = mapHelper(result, err, "Float64Map", func(n int) { m = make(map[string]float64, n) }, func(key string, v interface{}) error { value, err := Float64(v, nil) if err != nil { return err } m[key] = value return nil }, ) return m, err } // Positions is a helper that converts an array of positions (lat, long) // into a [][2]float64. The GEOPOS command returns replies in this format. func Positions(result interface{}, err error) ([]*[2]float64, error) { values, err := Values(result, err) if err != nil { return nil, err } positions := make([]*[2]float64, len(values)) for i := range values { if values[i] == nil { continue } p, ok := values[i].([]interface{}) if !ok { return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) } if len(p) != 2 { return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) } lat, err := Float64(p[0], nil) if err != nil { return nil, err } long, err := Float64(p[1], nil) if err != nil { return nil, err } positions[i] = &[2]float64{lat, long} } return positions, nil } // Uint64s is a helper that converts an array command reply to a []uint64. // If err is not equal to nil, then Uint64s returns nil, err. Nil array // items are stay nil. Uint64s returns an error if an array item is not a // bulk string or nil. func Uint64s(reply interface{}, err error) ([]uint64, error) { var result []uint64 err = sliceHelper(reply, err, "Uint64s", func(n int) { result = make([]uint64, n) }, func(i int, v interface{}) error { switch v := v.(type) { case uint64: result[i] = v return nil case []byte: n, err := strconv.ParseUint(string(v), 10, 64) result[i] = n return err case Error: return v default: return fmt.Errorf("redigo: unexpected element type for Uint64s, got type %T", v) } }) return result, err } // Uint64Map is a helper that converts an array of strings (alternating key, value) // into a map[string]uint64. The HGETALL commands return replies in this format. // Requires an even number of values in result. func Uint64Map(result interface{}, err error) (map[string]uint64, error) { var m map[string]uint64 err = mapHelper(result, err, "Uint64Map", func(n int) { m = make(map[string]uint64, n) }, func(key string, v interface{}) error { value, err := Uint64(v, nil) if err != nil { return err } m[key] = value return nil }, ) return m, err } // SlowLogs is a helper that parse the SLOWLOG GET command output and // return the array of SlowLog func SlowLogs(result interface{}, err error) ([]SlowLog, error) { rawLogs, err := Values(result, err) if err != nil { return nil, err } logs := make([]SlowLog, len(rawLogs)) for i, e := range rawLogs { rawLog, ok := e.([]interface{}) if !ok { return nil, fmt.Errorf("redigo: slowlog element is not an array, got %T", e) } var log SlowLog if len(rawLog) < 4 { return nil, fmt.Errorf("redigo: slowlog element has %d elements, expected at least 4", len(rawLog)) } log.ID, ok = rawLog[0].(int64) if !ok { return nil, fmt.Errorf("redigo: slowlog element[0] not an int64, got %T", rawLog[0]) } timestamp, ok := rawLog[1].(int64) if !ok { return nil, fmt.Errorf("redigo: slowlog element[1] not an int64, got %T", rawLog[1]) } log.Time = time.Unix(timestamp, 0) duration, ok := rawLog[2].(int64) if !ok { return nil, fmt.Errorf("redigo: slowlog element[2] not an int64, got %T", rawLog[2]) } log.ExecutionTime = time.Duration(duration) * time.Microsecond log.Args, err = Strings(rawLog[3], nil) if err != nil { return nil, fmt.Errorf("redigo: slowlog element[3] is not array of strings: %w", err) } if len(rawLog) >= 6 { log.ClientAddr, err = String(rawLog[4], nil) if err != nil { return nil, fmt.Errorf("redigo: slowlog element[4] is not a string: %w", err) } log.ClientName, err = String(rawLog[5], nil) if err != nil { return nil, fmt.Errorf("redigo: slowlog element[5] is not a string: %w", err) } } logs[i] = log } return logs, nil } // Latencies is a helper that parses the LATENCY LATEST command output and // return the slice of Latency values. func Latencies(result interface{}, err error) ([]Latency, error) { rawLatencies, err := Values(result, err) if err != nil { return nil, err } latencies := make([]Latency, len(rawLatencies)) for i, e := range rawLatencies { rawLatency, ok := e.([]interface{}) if !ok { return nil, fmt.Errorf("redigo: latencies element is not slice, got %T", e) } var event Latency if len(rawLatency) != 4 { return nil, fmt.Errorf("redigo: latencies element has %d elements, expected 4", len(rawLatency)) } event.Name, err = String(rawLatency[0], nil) if err != nil { return nil, fmt.Errorf("redigo: latencies element[0] is not a string: %w", err) } timestamp, ok := rawLatency[1].(int64) if !ok { return nil, fmt.Errorf("redigo: latencies element[1] not an int64, got %T", rawLatency[1]) } event.Time = time.Unix(timestamp, 0) latestDuration, ok := rawLatency[2].(int64) if !ok { return nil, fmt.Errorf("redigo: latencies element[2] not an int64, got %T", rawLatency[2]) } event.Latest = time.Duration(latestDuration) * time.Millisecond maxDuration, ok := rawLatency[3].(int64) if !ok { return nil, fmt.Errorf("redigo: latencies element[3] not an int64, got %T", rawLatency[3]) } event.Max = time.Duration(maxDuration) * time.Millisecond latencies[i] = event } return latencies, nil } // LatencyHistories is a helper that parse the LATENCY HISTORY command output and // returns a LatencyHistory slice. func LatencyHistories(result interface{}, err error) ([]LatencyHistory, error) { rawLogs, err := Values(result, err) if err != nil { return nil, err } latencyHistories := make([]LatencyHistory, len(rawLogs)) for i, e := range rawLogs { rawLog, ok := e.([]interface{}) if !ok { return nil, fmt.Errorf("redigo: latency history element is not an slice, got %T", e) } var event LatencyHistory timestamp, ok := rawLog[0].(int64) if !ok { return nil, fmt.Errorf("redigo: latency history element[0] not an int64, got %T", rawLog[0]) } event.Time = time.Unix(timestamp, 0) duration, ok := rawLog[1].(int64) if !ok { return nil, fmt.Errorf("redigo: latency history element[1] not an int64, got %T", rawLog[1]) } event.ExecutionTime = time.Duration(duration) * time.Millisecond latencyHistories[i] = event } return latencyHistories, nil } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/scan.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "errors" "fmt" "reflect" "strconv" "strings" "sync" ) var ( scannerType = reflect.TypeOf((*Scanner)(nil)).Elem() ) func ensureLen(d reflect.Value, n int) { if n > d.Cap() { d.Set(reflect.MakeSlice(d.Type(), n, n)) } else { d.SetLen(n) } } func cannotConvert(d reflect.Value, s interface{}) error { var sname string switch s.(type) { case string: sname = "Redis simple string" case Error: sname = "Redis error" case int64: sname = "Redis integer" case []byte: sname = "Redis bulk string" case []interface{}: sname = "Redis array" case nil: sname = "Redis nil" default: sname = reflect.TypeOf(s).String() } return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) } func convertAssignNil(d reflect.Value) (err error) { switch d.Type().Kind() { case reflect.Slice, reflect.Interface: d.Set(reflect.Zero(d.Type())) default: err = cannotConvert(d, nil) } return err } func convertAssignError(d reflect.Value, s Error) (err error) { if d.Kind() == reflect.String { d.SetString(string(s)) } else if d.Kind() == reflect.Slice && d.Type().Elem().Kind() == reflect.Uint8 { d.SetBytes([]byte(s)) } else { err = cannotConvert(d, s) } return } func convertAssignString(d reflect.Value, s string) (err error) { switch d.Type().Kind() { case reflect.Float32, reflect.Float64: var x float64 x, err = strconv.ParseFloat(s, d.Type().Bits()) d.SetFloat(x) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var x int64 x, err = strconv.ParseInt(s, 10, d.Type().Bits()) d.SetInt(x) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var x uint64 x, err = strconv.ParseUint(s, 10, d.Type().Bits()) d.SetUint(x) case reflect.Bool: var x bool x, err = strconv.ParseBool(s) d.SetBool(x) case reflect.String: d.SetString(s) case reflect.Slice: if d.Type().Elem().Kind() == reflect.Uint8 { d.SetBytes([]byte(s)) } else { err = cannotConvert(d, s) } case reflect.Ptr: err = convertAssignString(d.Elem(), s) default: err = cannotConvert(d, s) } return } func convertAssignBulkString(d reflect.Value, s []byte) (err error) { switch d.Type().Kind() { case reflect.Slice: // Handle []byte destination here to avoid unnecessary // []byte -> string -> []byte converion. if d.Type().Elem().Kind() == reflect.Uint8 { d.SetBytes(s) } else { err = cannotConvert(d, s) } case reflect.Ptr: if d.CanInterface() && d.CanSet() { if s == nil { if d.IsNil() { return nil } d.Set(reflect.Zero(d.Type())) return nil } if d.IsNil() { d.Set(reflect.New(d.Type().Elem())) } if sc, ok := d.Interface().(Scanner); ok { return sc.RedisScan(s) } } err = convertAssignString(d, string(s)) default: err = convertAssignString(d, string(s)) } return err } func convertAssignInt(d reflect.Value, s int64) (err error) { switch d.Type().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: d.SetInt(s) if d.Int() != s { err = strconv.ErrRange d.SetInt(0) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if s < 0 { err = strconv.ErrRange } else { x := uint64(s) d.SetUint(x) if d.Uint() != x { err = strconv.ErrRange d.SetUint(0) } } case reflect.Bool: d.SetBool(s != 0) default: err = cannotConvert(d, s) } return } func convertAssignValue(d reflect.Value, s interface{}) (err error) { if d.Kind() != reflect.Ptr { if d.CanAddr() { d2 := d.Addr() if d2.CanInterface() { if scanner, ok := d2.Interface().(Scanner); ok { return scanner.RedisScan(s) } } } } else if d.CanInterface() { // Already a reflect.Ptr if d.IsNil() { d.Set(reflect.New(d.Type().Elem())) } if scanner, ok := d.Interface().(Scanner); ok { return scanner.RedisScan(s) } } switch s := s.(type) { case nil: err = convertAssignNil(d) case []byte: err = convertAssignBulkString(d, s) case int64: err = convertAssignInt(d, s) case string: err = convertAssignString(d, s) case Error: err = convertAssignError(d, s) default: err = cannotConvert(d, s) } return err } func convertAssignArray(d reflect.Value, s []interface{}) error { if d.Type().Kind() != reflect.Slice { return cannotConvert(d, s) } ensureLen(d, len(s)) for i := 0; i < len(s); i++ { if err := convertAssignValue(d.Index(i), s[i]); err != nil { return err } } return nil } func convertAssign(d interface{}, s interface{}) (err error) { if scanner, ok := d.(Scanner); ok { return scanner.RedisScan(s) } // Handle the most common destination types using type switches and // fall back to reflection for all other types. switch s := s.(type) { case nil: // ignore case []byte: switch d := d.(type) { case *string: *d = string(s) case *int: *d, err = strconv.Atoi(string(s)) case *bool: *d, err = strconv.ParseBool(string(s)) case *[]byte: *d = s case *interface{}: *d = s case nil: // skip value default: if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { err = convertAssignBulkString(d.Elem(), s) } } case int64: switch d := d.(type) { case *int: x := int(s) if int64(x) != s { err = strconv.ErrRange x = 0 } *d = x case *bool: *d = s != 0 case *interface{}: *d = s case nil: // skip value default: if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { err = convertAssignInt(d.Elem(), s) } } case string: switch d := d.(type) { case *string: *d = s case *interface{}: *d = s case nil: // skip value default: err = cannotConvert(reflect.ValueOf(d), s) } case []interface{}: switch d := d.(type) { case *[]interface{}: *d = s case *interface{}: *d = s case nil: // skip value default: if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { err = convertAssignArray(d.Elem(), s) } } case Error: err = s default: err = cannotConvert(reflect.ValueOf(d), s) } return } // Scan copies from src to the values pointed at by dest. // // Scan uses RedisScan if available otherwise: // // The values pointed at by dest must be an integer, float, boolean, string, // []byte, interface{} or slices of these types. Scan uses the standard strconv // package to convert bulk strings to numeric and boolean types. // // If a dest value is nil, then the corresponding src value is skipped. // // If a src element is nil, then the corresponding dest value is not modified. // // To enable easy use of Scan in a loop, Scan returns the slice of src // following the copied values. func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { if len(src) < len(dest) { return nil, errors.New("redigo.Scan: array short") } var err error for i, d := range dest { err = convertAssign(d, src[i]) if err != nil { err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) break } } return src[len(dest):], err } type fieldSpec struct { name string index []int omitEmpty bool } type structSpec struct { m map[string]*fieldSpec l []*fieldSpec } func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { return ss.m[string(name)] } func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec, seen map[reflect.Type]struct{}) error { if _, ok := seen[t]; ok { // Protect against infinite recursion. return fmt.Errorf("recursive struct definition for %v", t) } seen[t] = struct{}{} LOOP: for i := 0; i < t.NumField(); i++ { f := t.Field(i) switch { case f.PkgPath != "" && !f.Anonymous: // Ignore unexported fields. case f.Anonymous: switch f.Type.Kind() { case reflect.Struct: if err := compileStructSpec(f.Type, depth, append(index, i), ss, seen); err != nil { return err } case reflect.Ptr: if f.Type.Elem().Kind() == reflect.Struct { if err := compileStructSpec(f.Type.Elem(), depth, append(index, i), ss, seen); err != nil { return err } } } default: fs := &fieldSpec{name: f.Name} tag := f.Tag.Get("redis") var p string first := true for len(tag) > 0 { i := strings.IndexByte(tag, ',') if i < 0 { p, tag = tag, "" } else { p, tag = tag[:i], tag[i+1:] } if p == "-" { continue LOOP } if first && len(p) > 0 { fs.name = p first = false } else { switch p { case "omitempty": fs.omitEmpty = true default: panic(fmt.Errorf("redigo: unknown field tag %s for type %s", p, t.Name())) } } } d, found := depth[fs.name] if !found { d = 1 << 30 } switch { case len(index) == d: // At same depth, remove from result. delete(ss.m, fs.name) j := 0 for i := 0; i < len(ss.l); i++ { if fs.name != ss.l[i].name { ss.l[j] = ss.l[i] j += 1 } } ss.l = ss.l[:j] case len(index) < d: fs.index = make([]int, len(index)+1) copy(fs.index, index) fs.index[len(index)] = i depth[fs.name] = len(index) ss.m[fs.name] = fs ss.l = append(ss.l, fs) } } } return nil } var ( structSpecMutex sync.RWMutex structSpecCache = make(map[reflect.Type]*structSpec) ) func structSpecForType(t reflect.Type) (*structSpec, error) { structSpecMutex.RLock() ss, found := structSpecCache[t] structSpecMutex.RUnlock() if found { return ss, nil } structSpecMutex.Lock() defer structSpecMutex.Unlock() ss, found = structSpecCache[t] if found { return ss, nil } ss = &structSpec{m: make(map[string]*fieldSpec)} if err := compileStructSpec(t, make(map[string]int), nil, ss, make(map[reflect.Type]struct{})); err != nil { return nil, fmt.Errorf("compile struct: %s: %w", t, err) } structSpecCache[t] = ss return ss, nil } var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") // ScanStruct scans alternating names and values from src to a struct. The // HGETALL and CONFIG GET commands return replies in this format. // // ScanStruct uses exported field names to match values in the response. Use // 'redis' field tag to override the name: // // Field int `redis:"myName"` // // Fields with the tag redis:"-" are ignored. // // Each field uses RedisScan if available otherwise: // Integer, float, boolean, string and []byte fields are supported. Scan uses the // standard strconv package to convert bulk string values to numeric and // boolean types. // // If a src element is nil, then the corresponding field is not modified. func ScanStruct(src []interface{}, dest interface{}) error { d := reflect.ValueOf(dest) if d.Kind() != reflect.Ptr || d.IsNil() { return errScanStructValue } d = d.Elem() if d.Kind() != reflect.Struct { return errScanStructValue } if len(src)%2 != 0 { return errors.New("redigo.ScanStruct: number of values not a multiple of 2") } ss, err := structSpecForType(d.Type()) if err != nil { return fmt.Errorf("redigo.ScanStruct: %w", err) } for i := 0; i < len(src); i += 2 { s := src[i+1] if s == nil { continue } name, ok := src[i].([]byte) if !ok { return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) } fs := ss.fieldSpec(name) if fs == nil { continue } if err := convertAssignValue(fieldByIndexCreate(d, fs.index), s); err != nil { return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) } } return nil } var ( errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") ) // ScanSlice scans src to the slice pointed to by dest. // // If the target is a slice of types which implement Scanner then the custom // RedisScan method is used otherwise the following rules apply: // // The elements in the dest slice must be integer, float, boolean, string, struct // or pointer to struct values. // // Struct fields must be integer, float, boolean or string values. All struct // fields are used unless a subset is specified using fieldNames. func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { d := reflect.ValueOf(dest) if d.Kind() != reflect.Ptr || d.IsNil() { return errScanSliceValue } d = d.Elem() if d.Kind() != reflect.Slice { return errScanSliceValue } isPtr := false t := d.Type().Elem() st := t if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { isPtr = true t = t.Elem() } if t.Kind() != reflect.Struct || st.Implements(scannerType) { ensureLen(d, len(src)) for i, s := range src { if s == nil { continue } if err := convertAssignValue(d.Index(i), s); err != nil { return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) } } return nil } ss, err := structSpecForType(t) if err != nil { return fmt.Errorf("redigo.ScanSlice: %w", err) } fss := ss.l if len(fieldNames) > 0 { fss = make([]*fieldSpec, len(fieldNames)) for i, name := range fieldNames { fss[i] = ss.m[name] if fss[i] == nil { return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) } } } if len(fss) == 0 { return errors.New("redigo.ScanSlice: no struct fields") } n := len(src) / len(fss) if n*len(fss) != len(src) { return errors.New("redigo.ScanSlice: length not a multiple of struct field count") } ensureLen(d, n) for i := 0; i < n; i++ { d := d.Index(i) if isPtr { if d.IsNil() { d.Set(reflect.New(t)) } d = d.Elem() } for j, fs := range fss { s := src[i*len(fss)+j] if s == nil { continue } if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) } } } return nil } // Args is a helper for constructing command arguments from structured values. type Args []interface{} // Add returns the result of appending value to args. func (args Args) Add(value ...interface{}) Args { return append(args, value...) } // AddFlat returns the result of appending the flattened value of v to args. // // Maps are flattened by appending the alternating keys and map values to args. // // Slices are flattened by appending the slice elements to args. // // Structs are flattened by appending the alternating names and values of // exported fields to args. If v is a nil struct pointer, then nothing is // appended. The 'redis' field tag overrides struct field names. See ScanStruct // for more information on the use of the 'redis' field tag. // // Other types are appended to args as is. // panics if v includes a recursive anonymous struct. func (args Args) AddFlat(v interface{}) Args { rv := reflect.ValueOf(v) switch rv.Kind() { case reflect.Struct: args = flattenStruct(args, rv) case reflect.Slice: for i := 0; i < rv.Len(); i++ { args = append(args, rv.Index(i).Interface()) } case reflect.Map: for _, k := range rv.MapKeys() { args = append(args, k.Interface(), rv.MapIndex(k).Interface()) } case reflect.Ptr: if rv.Type().Elem().Kind() == reflect.Struct { if !rv.IsNil() { args = flattenStruct(args, rv.Elem()) } } else { args = append(args, v) } default: args = append(args, v) } return args } func flattenStruct(args Args, v reflect.Value) Args { ss, err := structSpecForType(v.Type()) if err != nil { panic(fmt.Errorf("redigo.AddFlat: %w", err)) } for _, fs := range ss.l { fv, err := fieldByIndexErr(v, fs.index) if err != nil { // Nil item ignore. continue } if fs.omitEmpty { var empty = false switch fv.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: empty = fv.Len() == 0 case reflect.Bool: empty = !fv.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: empty = fv.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: empty = fv.Uint() == 0 case reflect.Float32, reflect.Float64: empty = fv.Float() == 0 case reflect.Interface, reflect.Ptr: empty = fv.IsNil() } if empty { continue } } if arg, ok := fv.Interface().(Argument); ok { args = append(args, fs.name, arg.RedisArg()) } else if fv.Kind() == reflect.Ptr { if !fv.IsNil() { args = append(args, fs.name, fv.Elem().Interface()) } } else { args = append(args, fs.name, fv.Interface()) } } return args } ================================================ FILE: examples/web/vendor/github.com/gomodule/redigo/redis/script.go ================================================ // Copyright 2012 Gary Burd // // 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. package redis import ( "context" "crypto/sha1" "encoding/hex" "io" "strings" ) // Script encapsulates the source, hash and key count for a Lua script. See // http://redis.io/commands/eval for information on scripts in Redis. type Script struct { keyCount int src string hash string } // NewScript returns a new script object. If keyCount is greater than or equal // to zero, then the count is automatically inserted in the EVAL command // argument list. If keyCount is less than zero, then the application supplies // the count as the first value in the keysAndArgs argument to the Do, Send and // SendHash methods. func NewScript(keyCount int, src string) *Script { h := sha1.New() io.WriteString(h, src) // nolint: errcheck return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} } func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { var args []interface{} if s.keyCount < 0 { args = make([]interface{}, 1+len(keysAndArgs)) args[0] = spec copy(args[1:], keysAndArgs) } else { args = make([]interface{}, 2+len(keysAndArgs)) args[0] = spec args[1] = s.keyCount copy(args[2:], keysAndArgs) } return args } // Hash returns the script hash. func (s *Script) Hash() string { return s.hash } func (s *Script) DoContext(ctx context.Context, c Conn, keysAndArgs ...interface{}) (interface{}, error) { cwt, ok := c.(ConnWithContext) if !ok { return nil, errContextNotSupported } v, err := cwt.DoContext(ctx, "EVALSHA", s.args(s.hash, keysAndArgs)...) if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { v, err = cwt.DoContext(ctx, "EVAL", s.args(s.src, keysAndArgs)...) } return v, err } // Do evaluates the script. Under the covers, Do optimistically evaluates the // script using the EVALSHA command. If the command fails because the script is // not loaded, then Do evaluates the script using the EVAL command (thus // causing the script to load). func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) if err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) } return v, err } // SendHash evaluates the script without waiting for the reply. The script is // evaluated with the EVALSHA command. The application must ensure that the // script is loaded by a previous call to Send, Do or Load methods. func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) } // Send evaluates the script without waiting for the reply. func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { return c.Send("EVAL", s.args(s.src, keysAndArgs)...) } // Load loads the script without evaluating it. func (s *Script) Load(c Conn) error { _, err := c.Do("SCRIPT", "LOAD", s.src) return err } ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/.editorconfig ================================================ ; https://editorconfig.org/ root = true [*] insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = space indent_size = 2 [{Makefile,go.mod,go.sum,*.go,.gitmodules}] indent_style = tab indent_size = 4 [*.md] indent_size = 4 trim_trailing_whitespace = false eclint_indent_style = unset ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/.gitignore ================================================ coverage.coverprofile ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/LICENSE ================================================ Copyright (c) 2023 The Gorilla Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/Makefile ================================================ GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest GO_SEC=$(shell which gosec 2> /dev/null || echo '') GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest .PHONY: golangci-lint golangci-lint: $(if $(GO_LINT), ,go install $(GO_LINT_URI)) @echo "##### Running golangci-lint" golangci-lint run -v .PHONY: gosec gosec: $(if $(GO_SEC), ,go install $(GO_SEC_URI)) @echo "##### Running gosec" gosec ./... .PHONY: govulncheck govulncheck: $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) @echo "##### Running govulncheck" govulncheck ./... .PHONY: verify verify: golangci-lint gosec govulncheck .PHONY: test test: @echo "##### Running tests" go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/README.md ================================================ # gorilla/mux ![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg) [![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux) [![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) [![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) ![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to their respective handler. The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. * URL hosts, paths and query values can have variables with an optional regular expression. * Registered URLs can be built, or "reversed", which helps maintaining references to resources. * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. --- * [Install](#install) * [Examples](#examples) * [Matching Routes](#matching-routes) * [Static Files](#static-files) * [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.) * [Registered URLs](#registered-urls) * [Walking Routes](#walking-routes) * [Graceful Shutdown](#graceful-shutdown) * [Middleware](#middleware) * [Handling CORS Requests](#handling-cors-requests) * [Testing Handlers](#testing-handlers) * [Full Example](#full-example) --- ## Install With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: ```sh go get -u github.com/gorilla/mux ``` ## Examples Let's start registering a couple of URL paths and handlers: ```go func main() { r := mux.NewRouter() r.HandleFunc("/", HomeHandler) r.HandleFunc("/products", ProductsHandler) r.HandleFunc("/articles", ArticlesHandler) http.Handle("/", r) } ``` Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters. Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: ```go r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) ``` The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: ```go func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Category: %v\n", vars["category"]) } ``` And this is all you need to know about the basic usage. More advanced options are explained below. ### Matching Routes Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: ```go r := mux.NewRouter() // Only matches if domain is "www.example.com". r.Host("www.example.com") // Matches a dynamic subdomain. r.Host("{subdomain:[a-z]+}.example.com") ``` There are several other matchers that can be added. To match path prefixes: ```go r.PathPrefix("/products/") ``` ...or HTTP methods: ```go r.Methods("GET", "POST") ``` ...or URL schemes: ```go r.Schemes("https") ``` ...or header values: ```go r.Headers("X-Requested-With", "XMLHttpRequest") ``` ...or query values: ```go r.Queries("key", "value") ``` ...or to use a custom matcher function: ```go r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 0 }) ``` ...and finally, it is possible to combine several matchers in a single route: ```go r.HandleFunc("/products", ProductsHandler). Host("www.example.com"). Methods("GET"). Schemes("http") ``` Routes are tested in the order they were added to the router. If two routes match, the first one wins: ```go r := mux.NewRouter() r.HandleFunc("/specific", specificHandler) r.PathPrefix("/").Handler(catchAllHandler) ``` Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it: ```go r := mux.NewRouter() s := r.Host("www.example.com").Subrouter() ``` Then register routes in the subrouter: ```go s.HandleFunc("/products/", ProductsHandler) s.HandleFunc("/products/{key}", ProductHandler) s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) ``` The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: ```go r := mux.NewRouter() s := r.PathPrefix("/products").Subrouter() // "/products/" s.HandleFunc("/", ProductsHandler) // "/products/{key}/" s.HandleFunc("/{key}/", ProductHandler) // "/products/{key}/details" s.HandleFunc("/{key}/details", ProductDetailsHandler) ``` ### Static Files Note that the path provided to `PathPrefix()` represents a "wildcard": calling `PathPrefix("/static/").Handler(...)` means that the handler will be passed any request that matches "/static/\*". This makes it easy to serve static files with mux: ```go func main() { var dir string flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") flag.Parse() r := mux.NewRouter() // This will serve files under http://localhost:8000/static/ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8000", // Good practice: enforce timeouts for servers you create! WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } log.Fatal(srv.ListenAndServe()) } ``` ### Serving Single Page Applications Most of the time it makes sense to serve your SPA on a separate web server from your API, but sometimes it's desirable to serve them both from one place. It's possible to write a simple handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage mux's powerful routing for your API endpoints. ```go package main import ( "encoding/json" "log" "net/http" "os" "path/filepath" "time" "github.com/gorilla/mux" ) // spaHandler implements the http.Handler interface, so we can use it // to respond to HTTP requests. The path to the static directory and // path to the index file within that static directory are used to // serve the SPA in the given static directory. type spaHandler struct { staticPath string indexPath string } // ServeHTTP inspects the URL path to locate a file within the static dir // on the SPA handler. If a file is found, it will be served. If not, the // file located at the index path on the SPA handler will be served. This // is suitable behavior for serving an SPA (single page application). func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Join internally call path.Clean to prevent directory traversal path := filepath.Join(h.staticPath, r.URL.Path) // check whether a file exists or is a directory at the given path fi, err := os.Stat(path) if os.IsNotExist(err) || fi.IsDir() { // file does not exist or path is a directory, serve index.html http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) return } if err != nil { // if we got an error (that wasn't that the file doesn't exist) stating the // file, return a 500 internal server error and stop http.Error(w, err.Error(), http.StatusInternalServerError) return } // otherwise, use http.FileServer to serve the static file http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) } func main() { router := mux.NewRouter() router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { // an example API handler json.NewEncoder(w).Encode(map[string]bool{"ok": true}) }) spa := spaHandler{staticPath: "build", indexPath: "index.html"} router.PathPrefix("/").Handler(spa) srv := &http.Server{ Handler: router, Addr: "127.0.0.1:8000", // Good practice: enforce timeouts for servers you create! WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } log.Fatal(srv.ListenAndServe()) } ``` ### Registered URLs Now let's see how to build registered URLs. Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: ```go r := mux.NewRouter() r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). Name("article") ``` To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: ```go url, err := r.Get("article").URL("category", "technology", "id", "42") ``` ...and the result will be a `url.URL` with the following path: ``` "/articles/technology/42" ``` This also works for host and query value variables: ```go r := mux.NewRouter() r.Host("{subdomain}.example.com"). Path("/articles/{category}/{id:[0-9]+}"). Queries("filter", "{filter}"). HandlerFunc(ArticleHandler). Name("article") // url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42", "filter", "gorilla") ``` All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. Regex support also exists for matching Headers within a route. For example, we could do: ```go r.HeadersRegexp("Content-Type", "application/(text|json)") ``` ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do: ```go // "http://news.example.com/" host, err := r.Get("article").URLHost("subdomain", "news") // "/articles/technology/42" path, err := r.Get("article").URLPath("category", "technology", "id", "42") ``` And if you use subrouters, host and path defined separately can be built as well: ```go r := mux.NewRouter() s := r.Host("{subdomain}.example.com").Subrouter() s.Path("/articles/{category}/{id:[0-9]+}"). HandlerFunc(ArticleHandler). Name("article") // "http://news.example.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42") ``` To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available: ```go r := mux.NewRouter() r.Host("{domain}"). Path("/{group}/{item_id}"). Queries("some_data1", "{some_data1}"). Queries("some_data2", "{some_data2}"). Name("article") // Will print [domain group item_id some_data1 some_data2] fmt.Println(r.Get("article").GetVarNames()) ``` ### Walking Routes The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, the following prints all of the registered routes: ```go package main import ( "fmt" "net/http" "strings" "github.com/gorilla/mux" ) func handler(w http.ResponseWriter, r *http.Request) { return } func main() { r := mux.NewRouter() r.HandleFunc("/", handler) r.HandleFunc("/products", handler).Methods("POST") r.HandleFunc("/articles", handler).Methods("GET") r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT") r.HandleFunc("/authors", handler).Queries("surname", "{surname}") err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { pathTemplate, err := route.GetPathTemplate() if err == nil { fmt.Println("ROUTE:", pathTemplate) } pathRegexp, err := route.GetPathRegexp() if err == nil { fmt.Println("Path regexp:", pathRegexp) } queriesTemplates, err := route.GetQueriesTemplates() if err == nil { fmt.Println("Queries templates:", strings.Join(queriesTemplates, ",")) } queriesRegexps, err := route.GetQueriesRegexp() if err == nil { fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ",")) } methods, err := route.GetMethods() if err == nil { fmt.Println("Methods:", strings.Join(methods, ",")) } fmt.Println() return nil }) if err != nil { fmt.Println(err) } http.Handle("/", r) } ``` ### Graceful Shutdown Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`: ```go package main import ( "context" "flag" "log" "net/http" "os" "os/signal" "time" "github.com/gorilla/mux" ) func main() { var wait time.Duration flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") flag.Parse() r := mux.NewRouter() // Add your routes as needed srv := &http.Server{ Addr: "0.0.0.0:8080", // Good practice to set timeouts to avoid Slowloris attacks. WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, IdleTimeout: time.Second * 60, Handler: r, // Pass our instance of gorilla/mux in. } // Run our server in a goroutine so that it doesn't block. go func() { if err := srv.ListenAndServe(); err != nil { log.Println(err) } }() c := make(chan os.Signal, 1) // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. signal.Notify(c, os.Interrupt) // Block until we receive our signal. <-c // Create a deadline to wait for. ctx, cancel := context.WithTimeout(context.Background(), wait) defer cancel() // Doesn't block if no connections, but will otherwise wait // until the timeout deadline. srv.Shutdown(ctx) // Optionally, you could run srv.Shutdown in a goroutine and block on // <-ctx.Done() if your application should wait for other services // to finalize based on context cancellation. log.Println("shutting down") os.Exit(0) } ``` ### Middleware Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking. Mux middlewares are defined using the de facto standard type: ```go type MiddlewareFunc func(http.Handler) http.Handler ``` Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers. A very basic middleware which logs the URI of the request being handled could be written as: ```go func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here log.Println(r.RequestURI) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) } ``` Middlewares can be added to a router using `Router.Use()`: ```go r := mux.NewRouter() r.HandleFunc("/", handler) r.Use(loggingMiddleware) ``` A more complex authentication middleware, which maps session token to users, could be written as: ```go // Define our struct type authenticationMiddleware struct { tokenUsers map[string]string } // Initialize it somewhere func (amw *authenticationMiddleware) Populate() { amw.tokenUsers["00000000"] = "user0" amw.tokenUsers["aaaaaaaa"] = "userA" amw.tokenUsers["05f717e5"] = "randomUser" amw.tokenUsers["deadbeef"] = "user0" } // Middleware function, which will be called for each request func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Session-Token") if user, found := amw.tokenUsers[token]; found { // We found the token in our map log.Printf("Authenticated user %s\n", user) // Pass down the request to the next middleware (or final handler) next.ServeHTTP(w, r) } else { // Write an error and stop the handler chain http.Error(w, "Forbidden", http.StatusForbidden) } }) } ``` ```go r := mux.NewRouter() r.HandleFunc("/", handler) amw := authenticationMiddleware{tokenUsers: make(map[string]string)} amw.Populate() r.Use(amw.Middleware) ``` Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. ### Handling CORS Requests [CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header. * You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin` * The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route * If you do not specify any methods, then: > _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers. Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers: ```go package main import ( "net/http" "github.com/gorilla/mux" ) func main() { r := mux.NewRouter() // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions) r.Use(mux.CORSMethodMiddleware(r)) http.ListenAndServe(":8080", r) } func fooHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") if r.Method == http.MethodOptions { return } w.Write([]byte("foo")) } ``` And an request to `/foo` using something like: ```bash curl localhost:8080/foo -v ``` Would look like: ```bash * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) > GET /foo HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.59.0 > Accept: */* > < HTTP/1.1 200 OK < Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS < Access-Control-Allow-Origin: * < Date: Fri, 28 Jun 2019 20:13:30 GMT < Content-Length: 3 < Content-Type: text/plain; charset=utf-8 < * Connection #0 to host localhost left intact foo ``` ### Testing Handlers Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. First, our simple HTTP handler: ```go // endpoints.go package main func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { // A very simple health check. w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // In the future we could report back on the status of our DB, or our cache // (e.g. Redis) by performing a simple PING, and include them in the response. io.WriteString(w, `{"alive": true}`) } func main() { r := mux.NewRouter() r.HandleFunc("/health", HealthCheckHandler) log.Fatal(http.ListenAndServe("localhost:8080", r)) } ``` Our test code: ```go // endpoints_test.go package main import ( "net/http" "net/http/httptest" "testing" ) func TestHealthCheckHandler(t *testing.T) { // Create a request to pass to our handler. We don't have any query parameters for now, so we'll // pass 'nil' as the third parameter. req, err := http.NewRequest("GET", "/health", nil) if err != nil { t.Fatal(err) } // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. rr := httptest.NewRecorder() handler := http.HandlerFunc(HealthCheckHandler) // Our handlers satisfy http.Handler, so we can call their ServeHTTP method // directly and pass in our Request and ResponseRecorder. handler.ServeHTTP(rr, req) // Check the status code is what we expect. if status := rr.Code; status != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) } // Check the response body is what we expect. expected := `{"alive": true}` if rr.Body.String() != expected { t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) } } ``` In the case that our routes have [variables](#examples), we can pass those in the request. We could write [table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple possible route variables as needed. ```go // endpoints.go func main() { r := mux.NewRouter() // A route with a route variable: r.HandleFunc("/metrics/{type}", MetricsHandler) log.Fatal(http.ListenAndServe("localhost:8080", r)) } ``` Our test file, with a table-driven test of `routeVariables`: ```go // endpoints_test.go func TestMetricsHandler(t *testing.T) { tt := []struct{ routeVariable string shouldPass bool }{ {"goroutines", true}, {"heap", true}, {"counters", true}, {"queries", true}, {"adhadaeqm3k", false}, } for _, tc := range tt { path := fmt.Sprintf("/metrics/%s", tc.routeVariable) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() // To add the vars to the context, // we need to create a router through which we can pass the request. router := mux.NewRouter() router.HandleFunc("/metrics/{type}", MetricsHandler) router.ServeHTTP(rr, req) // In this case, our MetricsHandler returns a non-200 response // for a route variable it doesn't know about. if rr.Code == http.StatusOK && !tc.shouldPass { t.Errorf("handler should have failed on routeVariable %s: got %v want %v", tc.routeVariable, rr.Code, http.StatusOK) } } } ``` ## Full Example Here's a complete, runnable example of a small `mux` based server: ```go package main import ( "net/http" "log" "github.com/gorilla/mux" ) func YourHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\n")) } func main() { r := mux.NewRouter() // Routes consist of a path and a handler function. r.HandleFunc("/", YourHandler) // Bind to a port and pass our router in log.Fatal(http.ListenAndServe(":8000", r)) } ``` ## License BSD licensed. See the LICENSE file for details. ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/doc.go ================================================ // Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* Package mux implements a request router and dispatcher. The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: - Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. - URL hosts, paths and query values can have variables with an optional regular expression. - Registered URLs can be built, or "reversed", which helps maintaining references to resources. - Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. - It implements the http.Handler interface so it is compatible with the standard http.ServeMux. Let's start registering a couple of URL paths and handlers: func main() { r := mux.NewRouter() r.HandleFunc("/", HomeHandler) r.HandleFunc("/products", ProductsHandler) r.HandleFunc("/articles", ArticlesHandler) http.Handle("/", r) } Here we register three routes mapping URL paths to handlers. This is equivalent to how http.HandleFunc() works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (http.ResponseWriter, *http.Request) as parameters. Paths can have variables. They are defined using the format {name} or {name:pattern}. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) The names are used to create a map of route variables which can be retrieved calling mux.Vars(): vars := mux.Vars(request) category := vars["category"] Note that if any capturing groups are present, mux will panic() during parsing. To prevent this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to "/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably when capturing groups were present. And this is all you need to know about the basic usage. More advanced options are explained below. Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: r := mux.NewRouter() // Only matches if domain is "www.example.com". r.Host("www.example.com") // Matches a dynamic subdomain. r.Host("{subdomain:[a-z]+}.domain.com") There are several other matchers that can be added. To match path prefixes: r.PathPrefix("/products/") ...or HTTP methods: r.Methods("GET", "POST") ...or URL schemes: r.Schemes("https") ...or header values: r.Headers("X-Requested-With", "XMLHttpRequest") ...or query values: r.Queries("key", "value") ...or to use a custom matcher function: r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 0 }) ...and finally, it is possible to combine several matchers in a single route: r.HandleFunc("/products", ProductsHandler). Host("www.example.com"). Methods("GET"). Schemes("http") Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". For example, let's say we have several URLs that should only match when the host is "www.example.com". Create a route for that host and get a "subrouter" from it: r := mux.NewRouter() s := r.Host("www.example.com").Subrouter() Then register routes in the subrouter: s.HandleFunc("/products/", ProductsHandler) s.HandleFunc("/products/{key}", ProductHandler) s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) The three URL paths we registered above will only be tested if the domain is "www.example.com", because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: r := mux.NewRouter() s := r.PathPrefix("/products").Subrouter() // "/products/" s.HandleFunc("/", ProductsHandler) // "/products/{key}/" s.HandleFunc("/{key}/", ProductHandler) // "/products/{key}/details" s.HandleFunc("/{key}/details", ProductDetailsHandler) Note that the path provided to PathPrefix() represents a "wildcard": calling PathPrefix("/static/").Handler(...) means that the handler will be passed any request that matches "/static/*". This makes it easy to serve static files with mux: func main() { var dir string flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") flag.Parse() r := mux.NewRouter() // This will serve files under http://localhost:8000/static/ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8000", // Good practice: enforce timeouts for servers you create! WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } log.Fatal(srv.ListenAndServe()) } Now let's see how to build registered URLs. Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling Name() on a route. For example: r := mux.NewRouter() r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). Name("article") To build a URL, get the route and call the URL() method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: url, err := r.Get("article").URL("category", "technology", "id", "42") ...and the result will be a url.URL with the following path: "/articles/technology/42" This also works for host and query value variables: r := mux.NewRouter() r.Host("{subdomain}.domain.com"). Path("/articles/{category}/{id:[0-9]+}"). Queries("filter", "{filter}"). HandlerFunc(ArticleHandler). Name("article") // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42", "filter", "gorilla") All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. Regex support also exists for matching Headers within a route. For example, we could do: r.HeadersRegexp("Content-Type", "application/(text|json)") ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` There's also a way to build only the URL host or path for a route: use the methods URLHost() or URLPath() instead. For the previous route, we would do: // "http://news.domain.com/" host, err := r.Get("article").URLHost("subdomain", "news") // "/articles/technology/42" path, err := r.Get("article").URLPath("category", "technology", "id", "42") And if you use subrouters, host and path defined separately can be built as well: r := mux.NewRouter() s := r.Host("{subdomain}.domain.com").Subrouter() s.Path("/articles/{category}/{id:[0-9]+}"). HandlerFunc(ArticleHandler). Name("article") // "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", "category", "technology", "id", "42") Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking. type MiddlewareFunc func(http.Handler) http.Handler Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). A very basic middleware which logs the URI of the request being handled could be written as: func simpleMw(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here log.Println(r.RequestURI) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) } Middlewares can be added to a router using `Router.Use()`: r := mux.NewRouter() r.HandleFunc("/", handler) r.Use(simpleMw) A more complex authentication middleware, which maps session token to users, could be written as: // Define our struct type authenticationMiddleware struct { tokenUsers map[string]string } // Initialize it somewhere func (amw *authenticationMiddleware) Populate() { amw.tokenUsers["00000000"] = "user0" amw.tokenUsers["aaaaaaaa"] = "userA" amw.tokenUsers["05f717e5"] = "randomUser" amw.tokenUsers["deadbeef"] = "user0" } // Middleware function, which will be called for each request func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Session-Token") if user, found := amw.tokenUsers[token]; found { // We found the token in our map log.Printf("Authenticated user %s\n", user) next.ServeHTTP(w, r) } else { http.Error(w, "Forbidden", http.StatusForbidden) } }) } r := mux.NewRouter() r.HandleFunc("/", handler) amw := authenticationMiddleware{tokenUsers: make(map[string]string)} amw.Populate() r.Use(amw.Middleware) Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. */ package mux ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/middleware.go ================================================ package mux import ( "net/http" "strings" ) // MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. // Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed // to it, and then calls the handler passed as parameter to the MiddlewareFunc. type MiddlewareFunc func(http.Handler) http.Handler // middleware interface is anything which implements a MiddlewareFunc named Middleware. type middleware interface { Middleware(handler http.Handler) http.Handler } // Middleware allows MiddlewareFunc to implement the middleware interface. func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { return mw(handler) } // Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. func (r *Router) Use(mwf ...MiddlewareFunc) { for _, fn := range mwf { r.middlewares = append(r.middlewares, fn) } } // useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. func (r *Router) useInterface(mw middleware) { r.middlewares = append(r.middlewares, mw) } // CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header // on requests for routes that have an OPTIONS method matcher to all the method matchers on // the route. Routes that do not explicitly handle OPTIONS requests will not be processed // by the middleware. See examples for usage. func CORSMethodMiddleware(r *Router) MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { allMethods, err := getAllMethodsForRoute(r, req) if err == nil { for _, v := range allMethods { if v == http.MethodOptions { w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ",")) } } } next.ServeHTTP(w, req) }) } } // getAllMethodsForRoute returns all the methods from method matchers matching a given // request. func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) { var allMethods []string for _, route := range r.routes { var match RouteMatch if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch { methods, err := route.GetMethods() if err != nil { return nil, err } allMethods = append(allMethods, methods...) } } return allMethods, nil } ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/mux.go ================================================ // Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package mux import ( "context" "errors" "fmt" "net/http" "path" "regexp" ) var ( // ErrMethodMismatch is returned when the method in the request does not match // the method defined against the route. ErrMethodMismatch = errors.New("method is not allowed") // ErrNotFound is returned when no route match is found. ErrNotFound = errors.New("no matching route was found") ) // NewRouter returns a new router instance. func NewRouter() *Router { return &Router{namedRoutes: make(map[string]*Route)} } // Router registers routes to be matched and dispatches a handler. // // It implements the http.Handler interface, so it can be registered to serve // requests: // // var router = mux.NewRouter() // // func main() { // http.Handle("/", router) // } // // Or, for Google App Engine, register it in a init() function: // // func init() { // http.Handle("/", router) // } // // This will send all incoming requests to the router. type Router struct { // Configurable Handler to be used when no route matches. // This can be used to render your own 404 Not Found errors. NotFoundHandler http.Handler // Configurable Handler to be used when the request method does not match the route. // This can be used to render your own 405 Method Not Allowed errors. MethodNotAllowedHandler http.Handler // Routes to be matched, in order. routes []*Route // Routes by name for URL building. namedRoutes map[string]*Route // If true, do not clear the request context after handling the request. // // Deprecated: No effect, since the context is stored on the request itself. KeepContext bool // Slice of middlewares to be called after a match is found middlewares []middleware // configuration shared with `Route` routeConf } // common route configuration shared between `Router` and `Route` type routeConf struct { // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to" useEncodedPath bool // If true, when the path pattern is "/path/", accessing "/path" will // redirect to the former and vice versa. strictSlash bool // If true, when the path pattern is "/path//to", accessing "/path//to" // will not redirect skipClean bool // Manager for the variables from host and path. regexp routeRegexpGroup // List of matchers. matchers []matcher // The scheme used when building URLs. buildScheme string buildVarsFunc BuildVarsFunc } // returns an effective deep copy of `routeConf` func copyRouteConf(r routeConf) routeConf { c := r if r.regexp.path != nil { c.regexp.path = copyRouteRegexp(r.regexp.path) } if r.regexp.host != nil { c.regexp.host = copyRouteRegexp(r.regexp.host) } c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries)) for _, q := range r.regexp.queries { c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q)) } c.matchers = make([]matcher, len(r.matchers)) copy(c.matchers, r.matchers) return c } func copyRouteRegexp(r *routeRegexp) *routeRegexp { c := *r return &c } // Match attempts to match the given request against the router's registered routes. // // If the request matches a route of this router or one of its subrouters the Route, // Handler, and Vars fields of the the match argument are filled and this function // returns true. // // If the request does not match any of this router's or its subrouters' routes // then this function returns false. If available, a reason for the match failure // will be filled in the match argument's MatchErr field. If the match failure type // (eg: not found) has a registered handler, the handler is assigned to the Handler // field of the match argument. func (r *Router) Match(req *http.Request, match *RouteMatch) bool { for _, route := range r.routes { if route.Match(req, match) { // Build middleware chain if no error was found if match.MatchErr == nil { for i := len(r.middlewares) - 1; i >= 0; i-- { match.Handler = r.middlewares[i].Middleware(match.Handler) } } return true } } if match.MatchErr == ErrMethodMismatch { if r.MethodNotAllowedHandler != nil { match.Handler = r.MethodNotAllowedHandler return true } return false } // Closest match for a router (includes sub-routers) if r.NotFoundHandler != nil { match.Handler = r.NotFoundHandler match.MatchErr = ErrNotFound return true } match.MatchErr = ErrNotFound return false } // ServeHTTP dispatches the handler registered in the matched route. // // When there is a match, the route variables can be retrieved calling // mux.Vars(request). func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if !r.skipClean { path := req.URL.Path if r.useEncodedPath { path = req.URL.EscapedPath() } // Clean path to canonical form and redirect. if p := cleanPath(path); p != path { // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: // http://code.google.com/p/go/issues/detail?id=5252 url := *req.URL url.Path = p p = url.String() w.Header().Set("Location", p) w.WriteHeader(http.StatusMovedPermanently) return } } var match RouteMatch var handler http.Handler if r.Match(req, &match) { handler = match.Handler req = requestWithVars(req, match.Vars) req = requestWithRoute(req, match.Route) } if handler == nil && match.MatchErr == ErrMethodMismatch { handler = methodNotAllowedHandler() } if handler == nil { handler = http.NotFoundHandler() } handler.ServeHTTP(w, req) } // Get returns a route registered with the given name. func (r *Router) Get(name string) *Route { return r.namedRoutes[name] } // GetRoute returns a route registered with the given name. This method // was renamed to Get() and remains here for backwards compatibility. func (r *Router) GetRoute(name string) *Route { return r.namedRoutes[name] } // StrictSlash defines the trailing slash behavior for new routes. The initial // value is false. // // When true, if the route path is "/path/", accessing "/path" will perform a redirect // to the former and vice versa. In other words, your application will always // see the path as specified in the route. // // When false, if the route path is "/path", accessing "/path/" will not match // this route and vice versa. // // The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for // routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed // request will be made as a GET by most clients. Use middleware or client settings // to modify this behaviour as needed. // // Special case: when a route sets a path prefix using the PathPrefix() method, // strict slash is ignored for that route because the redirect behavior can't // be determined from a prefix alone. However, any subrouters created from that // route inherit the original StrictSlash setting. func (r *Router) StrictSlash(value bool) *Router { r.strictSlash = value return r } // SkipClean defines the path cleaning behaviour for new routes. The initial // value is false. Users should be careful about which routes are not cleaned // // When true, if the route path is "/path//to", it will remain with the double // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ // // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will // become /fetch/http/xkcd.com/534 func (r *Router) SkipClean(value bool) *Router { r.skipClean = value return r } // UseEncodedPath tells the router to match the encoded original path // to the routes. // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". // // If not called, the router will match the unencoded path to the routes. // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" func (r *Router) UseEncodedPath() *Router { r.useEncodedPath = true return r } // ---------------------------------------------------------------------------- // Route factories // ---------------------------------------------------------------------------- // NewRoute registers an empty route. func (r *Router) NewRoute() *Route { // initialize a route with a copy of the parent router's configuration route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} r.routes = append(r.routes, route) return route } // Name registers a new route with a name. // See Route.Name(). func (r *Router) Name(name string) *Route { return r.NewRoute().Name(name) } // Handle registers a new route with a matcher for the URL path. // See Route.Path() and Route.Handler(). func (r *Router) Handle(path string, handler http.Handler) *Route { return r.NewRoute().Path(path).Handler(handler) } // HandleFunc registers a new route with a matcher for the URL path. // See Route.Path() and Route.HandlerFunc(). func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *Route { return r.NewRoute().Path(path).HandlerFunc(f) } // Headers registers a new route with a matcher for request header values. // See Route.Headers(). func (r *Router) Headers(pairs ...string) *Route { return r.NewRoute().Headers(pairs...) } // Host registers a new route with a matcher for the URL host. // See Route.Host(). func (r *Router) Host(tpl string) *Route { return r.NewRoute().Host(tpl) } // MatcherFunc registers a new route with a custom matcher function. // See Route.MatcherFunc(). func (r *Router) MatcherFunc(f MatcherFunc) *Route { return r.NewRoute().MatcherFunc(f) } // Methods registers a new route with a matcher for HTTP methods. // See Route.Methods(). func (r *Router) Methods(methods ...string) *Route { return r.NewRoute().Methods(methods...) } // Path registers a new route with a matcher for the URL path. // See Route.Path(). func (r *Router) Path(tpl string) *Route { return r.NewRoute().Path(tpl) } // PathPrefix registers a new route with a matcher for the URL path prefix. // See Route.PathPrefix(). func (r *Router) PathPrefix(tpl string) *Route { return r.NewRoute().PathPrefix(tpl) } // Queries registers a new route with a matcher for URL query values. // See Route.Queries(). func (r *Router) Queries(pairs ...string) *Route { return r.NewRoute().Queries(pairs...) } // Schemes registers a new route with a matcher for URL schemes. // See Route.Schemes(). func (r *Router) Schemes(schemes ...string) *Route { return r.NewRoute().Schemes(schemes...) } // BuildVarsFunc registers a new route with a custom function for modifying // route variables before building a URL. func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { return r.NewRoute().BuildVarsFunc(f) } // Walk walks the router and all its sub-routers, calling walkFn for each route // in the tree. The routes are walked in the order they were added. Sub-routers // are explored depth-first. func (r *Router) Walk(walkFn WalkFunc) error { return r.walk(walkFn, []*Route{}) } // SkipRouter is used as a return value from WalkFuncs to indicate that the // router that walk is about to descend down to should be skipped. var SkipRouter = errors.New("skip this router") // WalkFunc is the type of the function called for each route visited by Walk. // At every invocation, it is given the current route, and the current router, // and a list of ancestor routes that lead to the current route. type WalkFunc func(route *Route, router *Router, ancestors []*Route) error func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { for _, t := range r.routes { err := walkFn(t, r, ancestors) if err == SkipRouter { continue } if err != nil { return err } for _, sr := range t.matchers { if h, ok := sr.(*Router); ok { ancestors = append(ancestors, t) err := h.walk(walkFn, ancestors) if err != nil { return err } ancestors = ancestors[:len(ancestors)-1] } } if h, ok := t.handler.(*Router); ok { ancestors = append(ancestors, t) err := h.walk(walkFn, ancestors) if err != nil { return err } ancestors = ancestors[:len(ancestors)-1] } } return nil } // ---------------------------------------------------------------------------- // Context // ---------------------------------------------------------------------------- // RouteMatch stores information about a matched route. type RouteMatch struct { Route *Route Handler http.Handler Vars map[string]string // MatchErr is set to appropriate matching error // It is set to ErrMethodMismatch if there is a mismatch in // the request method and route method MatchErr error } type contextKey int const ( varsKey contextKey = iota routeKey ) // Vars returns the route variables for the current request, if any. func Vars(r *http.Request) map[string]string { if rv := r.Context().Value(varsKey); rv != nil { return rv.(map[string]string) } return nil } // CurrentRoute returns the matched route for the current request, if any. // This only works when called inside the handler of the matched route // because the matched route is stored in the request context which is cleared // after the handler returns. func CurrentRoute(r *http.Request) *Route { if rv := r.Context().Value(routeKey); rv != nil { return rv.(*Route) } return nil } func requestWithVars(r *http.Request, vars map[string]string) *http.Request { ctx := context.WithValue(r.Context(), varsKey, vars) return r.WithContext(ctx) } func requestWithRoute(r *http.Request, route *Route) *http.Request { ctx := context.WithValue(r.Context(), routeKey, route) return r.WithContext(ctx) } // ---------------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------------- // cleanPath returns the canonical path for p, eliminating . and .. elements. // Borrowed from the net/http package. func cleanPath(p string) string { if p == "" { return "/" } if p[0] != '/' { p = "/" + p } np := path.Clean(p) // path.Clean removes trailing slash except for root; // put the trailing slash back if necessary. if p[len(p)-1] == '/' && np != "/" { np += "/" } return np } // uniqueVars returns an error if two slices contain duplicated strings. func uniqueVars(s1, s2 []string) error { for _, v1 := range s1 { for _, v2 := range s2 { if v1 == v2 { return fmt.Errorf("mux: duplicated route variable %q", v2) } } } return nil } // checkPairs returns the count of strings passed in, and an error if // the count is not an even number. func checkPairs(pairs ...string) (int, error) { length := len(pairs) if length%2 != 0 { return length, fmt.Errorf( "mux: number of parameters must be multiple of 2, got %v", pairs) } return length, nil } // mapFromPairsToString converts variadic string parameters to a // string to string map. func mapFromPairsToString(pairs ...string) (map[string]string, error) { length, err := checkPairs(pairs...) if err != nil { return nil, err } m := make(map[string]string, length/2) for i := 0; i < length; i += 2 { m[pairs[i]] = pairs[i+1] } return m, nil } // mapFromPairsToRegex converts variadic string parameters to a // string to regex map. func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { length, err := checkPairs(pairs...) if err != nil { return nil, err } m := make(map[string]*regexp.Regexp, length/2) for i := 0; i < length; i += 2 { regex, err := regexp.Compile(pairs[i+1]) if err != nil { return nil, err } m[pairs[i]] = regex } return m, nil } // matchInArray returns true if the given string value is in the array. func matchInArray(arr []string, value string) bool { for _, v := range arr { if v == value { return true } } return false } // matchMapWithString returns true if the given key/value pairs exist in a given map. func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { for k, v := range toCheck { // Check if key exists. if canonicalKey { k = http.CanonicalHeaderKey(k) } if values := toMatch[k]; values == nil { return false } else if v != "" { // If value was defined as an empty string we only check that the // key exists. Otherwise we also check for equality. valueExists := false for _, value := range values { if v == value { valueExists = true break } } if !valueExists { return false } } } return true } // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against // the given regex func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { for k, v := range toCheck { // Check if key exists. if canonicalKey { k = http.CanonicalHeaderKey(k) } if values := toMatch[k]; values == nil { return false } else if v != nil { // If value was defined as an empty string we only check that the // key exists. Otherwise we also check for equality. valueExists := false for _, value := range values { if v.MatchString(value) { valueExists = true break } } if !valueExists { return false } } } return true } // methodNotAllowed replies to the request with an HTTP status code 405. func methodNotAllowed(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusMethodNotAllowed) } // methodNotAllowedHandler returns a simple request handler // that replies to each request with a status code 405. func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/regexp.go ================================================ // Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package mux import ( "bytes" "fmt" "net/http" "net/url" "regexp" "strconv" "strings" ) type routeRegexpOptions struct { strictSlash bool useEncodedPath bool } type regexpType int const ( regexpTypePath regexpType = iota regexpTypeHost regexpTypePrefix regexpTypeQuery ) // newRouteRegexp parses a route template and returns a routeRegexp, // used to match a host, a path or a query string. // // It will extract named variables, assemble a regexp to be matched, create // a "reverse" template to build URLs and compile regexps to validate variable // values used in URL building. // // Previously we accepted only Python-like identifiers for variable // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // name and pattern can't be empty, and names can't contain a colon. func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) if errBraces != nil { return nil, errBraces } // Backup the original. template := tpl // Now let's parse it. defaultPattern := "[^/]+" if typ == regexpTypeQuery { defaultPattern = ".*" } else if typ == regexpTypeHost { defaultPattern = "[^.]+" } // Only match strict slash if not matching if typ != regexpTypePath { options.strictSlash = false } // Set a flag for strictSlash. endSlash := false if options.strictSlash && strings.HasSuffix(tpl, "/") { tpl = tpl[:len(tpl)-1] endSlash = true } varsN := make([]string, len(idxs)/2) varsR := make([]*regexp.Regexp, len(idxs)/2) pattern := bytes.NewBufferString("") pattern.WriteByte('^') reverse := bytes.NewBufferString("") var end int var err error for i := 0; i < len(idxs); i += 2 { // Set all values we are interested in. raw := tpl[end:idxs[i]] end = idxs[i+1] parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) name := parts[0] patt := defaultPattern if len(parts) == 2 { patt = parts[1] } // Name or pattern can't be empty. if name == "" || patt == "" { return nil, fmt.Errorf("mux: missing name or pattern in %q", tpl[idxs[i]:end]) } // Build the regexp pattern. fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) // Build the reverse template. fmt.Fprintf(reverse, "%s%%s", raw) // Append variable name and compiled pattern. varsN[i/2] = name varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) if err != nil { return nil, err } } // Add the remaining. raw := tpl[end:] pattern.WriteString(regexp.QuoteMeta(raw)) if options.strictSlash { pattern.WriteString("[/]?") } if typ == regexpTypeQuery { // Add the default pattern if the query value is empty if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { pattern.WriteString(defaultPattern) } } if typ != regexpTypePrefix { pattern.WriteByte('$') } var wildcardHostPort bool if typ == regexpTypeHost { if !strings.Contains(pattern.String(), ":") { wildcardHostPort = true } } reverse.WriteString(raw) if endSlash { reverse.WriteByte('/') } // Compile full regexp. reg, errCompile := regexp.Compile(pattern.String()) if errCompile != nil { return nil, errCompile } // Check for capturing groups which used to work in older versions if reg.NumSubexp() != len(idxs)/2 { panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") } // Done! return &routeRegexp{ template: template, regexpType: typ, options: options, regexp: reg, reverse: reverse.String(), varsN: varsN, varsR: varsR, wildcardHostPort: wildcardHostPort, }, nil } // routeRegexp stores a regexp to match a host or path and information to // collect and validate route variables. type routeRegexp struct { // The unmodified template. template string // The type of match regexpType regexpType // Options for matching options routeRegexpOptions // Expanded regexp. regexp *regexp.Regexp // Reverse template. reverse string // Variable names. varsN []string // Variable regexps (validators). varsR []*regexp.Regexp // Wildcard host-port (no strict port match in hostname) wildcardHostPort bool } // Match matches the regexp against the URL host or path. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { if r.regexpType == regexpTypeHost { host := getHost(req) if r.wildcardHostPort { // Don't be strict on the port match if i := strings.Index(host, ":"); i != -1 { host = host[:i] } } return r.regexp.MatchString(host) } if r.regexpType == regexpTypeQuery { return r.matchQueryString(req) } path := req.URL.Path if r.options.useEncodedPath { path = req.URL.EscapedPath() } return r.regexp.MatchString(path) } // url builds a URL part using the given values. func (r *routeRegexp) url(values map[string]string) (string, error) { urlValues := make([]interface{}, len(r.varsN)) for k, v := range r.varsN { value, ok := values[v] if !ok { return "", fmt.Errorf("mux: missing route variable %q", v) } if r.regexpType == regexpTypeQuery { value = url.QueryEscape(value) } urlValues[k] = value } rv := fmt.Sprintf(r.reverse, urlValues...) if !r.regexp.MatchString(rv) { // The URL is checked against the full regexp, instead of checking // individual variables. This is faster but to provide a good error // message, we check individual regexps if the URL doesn't match. for k, v := range r.varsN { if !r.varsR[k].MatchString(values[v]) { return "", fmt.Errorf( "mux: variable %q doesn't match, expected %q", values[v], r.varsR[k].String()) } } } return rv, nil } // getURLQuery returns a single query parameter from a request URL. // For a URL with foo=bar&baz=ding, we return only the relevant key // value pair for the routeRegexp. func (r *routeRegexp) getURLQuery(req *http.Request) string { if r.regexpType != regexpTypeQuery { return "" } templateKey := strings.SplitN(r.template, "=", 2)[0] val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey) if ok { return templateKey + "=" + val } return "" } // findFirstQueryKey returns the same result as (*url.URL).Query()[key][0]. // If key was not found, empty string and false is returned. func findFirstQueryKey(rawQuery, key string) (value string, ok bool) { query := []byte(rawQuery) for len(query) > 0 { foundKey := query if i := bytes.IndexAny(foundKey, "&;"); i >= 0 { foundKey, query = foundKey[:i], foundKey[i+1:] } else { query = query[:0] } if len(foundKey) == 0 { continue } var value []byte if i := bytes.IndexByte(foundKey, '='); i >= 0 { foundKey, value = foundKey[:i], foundKey[i+1:] } if len(foundKey) < len(key) { // Cannot possibly be key. continue } keyString, err := url.QueryUnescape(string(foundKey)) if err != nil { continue } if keyString != key { continue } valueString, err := url.QueryUnescape(string(value)) if err != nil { continue } return valueString, true } return "", false } func (r *routeRegexp) matchQueryString(req *http.Request) bool { return r.regexp.MatchString(r.getURLQuery(req)) } // braceIndices returns the first level curly brace indices from a string. // It returns an error in case of unbalanced braces. func braceIndices(s string) ([]int, error) { var level, idx int var idxs []int for i := 0; i < len(s); i++ { switch s[i] { case '{': if level++; level == 1 { idx = i } case '}': if level--; level == 0 { idxs = append(idxs, idx, i+1) } else if level < 0 { return nil, fmt.Errorf("mux: unbalanced braces in %q", s) } } } if level != 0 { return nil, fmt.Errorf("mux: unbalanced braces in %q", s) } return idxs, nil } // varGroupName builds a capturing group name for the indexed variable. func varGroupName(idx int) string { return "v" + strconv.Itoa(idx) } // ---------------------------------------------------------------------------- // routeRegexpGroup // ---------------------------------------------------------------------------- // routeRegexpGroup groups the route matchers that carry variables. type routeRegexpGroup struct { host *routeRegexp path *routeRegexp queries []*routeRegexp } // setMatch extracts the variables from the URL once a route matches. func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { // Store host variables. if v.host != nil { host := getHost(req) if v.host.wildcardHostPort { // Don't be strict on the port match if i := strings.Index(host, ":"); i != -1 { host = host[:i] } } matches := v.host.regexp.FindStringSubmatchIndex(host) if len(matches) > 0 { extractVars(host, matches, v.host.varsN, m.Vars) } } path := req.URL.Path if r.useEncodedPath { path = req.URL.EscapedPath() } // Store path variables. if v.path != nil { matches := v.path.regexp.FindStringSubmatchIndex(path) if len(matches) > 0 { extractVars(path, matches, v.path.varsN, m.Vars) // Check if we should redirect. if v.path.options.strictSlash { p1 := strings.HasSuffix(path, "/") p2 := strings.HasSuffix(v.path.template, "/") if p1 != p2 { u, _ := url.Parse(req.URL.String()) if p1 { u.Path = u.Path[:len(u.Path)-1] } else { u.Path += "/" } m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently) } } } } // Store query string variables. for _, q := range v.queries { queryURL := q.getURLQuery(req) matches := q.regexp.FindStringSubmatchIndex(queryURL) if len(matches) > 0 { extractVars(queryURL, matches, q.varsN, m.Vars) } } } // getHost tries its best to return the request host. // According to section 14.23 of RFC 2616 the Host header // can include the port number if the default value of 80 is not used. func getHost(r *http.Request) string { if r.URL.IsAbs() { return r.URL.Host } return r.Host } func extractVars(input string, matches []int, names []string, output map[string]string) { for i, name := range names { output[name] = input[matches[2*i+2]:matches[2*i+3]] } } ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/route.go ================================================ // Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package mux import ( "errors" "fmt" "net/http" "net/url" "regexp" "strings" ) // Route stores information to match a request and build URLs. type Route struct { // Request handler for the route. handler http.Handler // If true, this route never matches: it is only used to build URLs. buildOnly bool // The name used to build URLs. name string // Error resulted from building a route. err error // "global" reference to all named routes namedRoutes map[string]*Route // config possibly passed in from `Router` routeConf } // SkipClean reports whether path cleaning is enabled for this route via // Router.SkipClean. func (r *Route) SkipClean() bool { return r.skipClean } // Match matches the route against the request. func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if r.buildOnly || r.err != nil { return false } var matchErr error // Match everything. for _, m := range r.matchers { if matched := m.Match(req, match); !matched { if _, ok := m.(methodMatcher); ok { matchErr = ErrMethodMismatch continue } // Ignore ErrNotFound errors. These errors arise from match call // to Subrouters. // // This prevents subsequent matching subrouters from failing to // run middleware. If not ignored, the middleware would see a // non-nil MatchErr and be skipped, even when there was a // matching route. if match.MatchErr == ErrNotFound { match.MatchErr = nil } matchErr = nil // nolint:ineffassign return false } else { // Multiple routes may share the same path but use different HTTP methods. For instance: // Route 1: POST "/users/{id}". // Route 2: GET "/users/{id}", parameters: "id": "[0-9]+". // // The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2", // The router should return a "Not Found" error as no route fully matches this request. if match.MatchErr == ErrMethodMismatch { match.MatchErr = nil } } } if matchErr != nil { match.MatchErr = matchErr return false } if match.MatchErr == ErrMethodMismatch && r.handler != nil { // We found a route which matches request method, clear MatchErr match.MatchErr = nil // Then override the mis-matched handler match.Handler = r.handler } // Yay, we have a match. Let's collect some info about it. if match.Route == nil { match.Route = r } if match.Handler == nil { match.Handler = r.handler } if match.Vars == nil { match.Vars = make(map[string]string) } // Set variables. r.regexp.setMatch(req, match, r) return true } // ---------------------------------------------------------------------------- // Route attributes // ---------------------------------------------------------------------------- // GetError returns an error resulted from building the route, if any. func (r *Route) GetError() error { return r.err } // BuildOnly sets the route to never match: it is only used to build URLs. func (r *Route) BuildOnly() *Route { r.buildOnly = true return r } // Handler -------------------------------------------------------------------- // Handler sets a handler for the route. func (r *Route) Handler(handler http.Handler) *Route { if r.err == nil { r.handler = handler } return r } // HandlerFunc sets a handler function for the route. func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { return r.Handler(http.HandlerFunc(f)) } // GetHandler returns the handler for the route, if any. func (r *Route) GetHandler() http.Handler { return r.handler } // Name ----------------------------------------------------------------------- // Name sets the name for the route, used to build URLs. // It is an error to call Name more than once on a route. func (r *Route) Name(name string) *Route { if r.name != "" { r.err = fmt.Errorf("mux: route already has name %q, can't set %q", r.name, name) } if r.err == nil { r.name = name r.namedRoutes[name] = r } return r } // GetName returns the name for the route, if any. func (r *Route) GetName() string { return r.name } // ---------------------------------------------------------------------------- // Matchers // ---------------------------------------------------------------------------- // matcher types try to match a request. type matcher interface { Match(*http.Request, *RouteMatch) bool } // addMatcher adds a matcher to the route. func (r *Route) addMatcher(m matcher) *Route { if r.err == nil { r.matchers = append(r.matchers, m) } return r } // addRegexpMatcher adds a host or path matcher and builder to a route. func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { if r.err != nil { return r.err } if typ == regexpTypePath || typ == regexpTypePrefix { if len(tpl) > 0 && tpl[0] != '/' { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } if r.regexp.path != nil { tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl } } rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ strictSlash: r.strictSlash, useEncodedPath: r.useEncodedPath, }) if err != nil { return err } for _, q := range r.regexp.queries { if err = uniqueVars(rr.varsN, q.varsN); err != nil { return err } } if typ == regexpTypeHost { if r.regexp.path != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { return err } } r.regexp.host = rr } else { if r.regexp.host != nil { if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { return err } } if typ == regexpTypeQuery { r.regexp.queries = append(r.regexp.queries, rr) } else { r.regexp.path = rr } } r.addMatcher(rr) return nil } // Headers -------------------------------------------------------------------- // headerMatcher matches the request against header values. type headerMatcher map[string]string func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchMapWithString(m, r.Header, true) } // Headers adds a matcher for request header values. // It accepts a sequence of key/value pairs to be matched. For example: // // r := mux.NewRouter().NewRoute() // r.Headers("Content-Type", "application/json", // "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both request header values match. // If the value is an empty string, it will match any value if the key is set. func (r *Route) Headers(pairs ...string) *Route { if r.err == nil { var headers map[string]string headers, r.err = mapFromPairsToString(pairs...) return r.addMatcher(headerMatcher(headers)) } return r } // headerRegexMatcher matches the request against the route given a regex for the header type headerRegexMatcher map[string]*regexp.Regexp func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchMapWithRegex(m, r.Header, true) } // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex // support. For example: // // r := mux.NewRouter().NewRoute() // r.HeadersRegexp("Content-Type", "application/(text|json)", // "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both the request header matches both regular expressions. // If the value is an empty string, it will match any value if the key is set. // Use the start and end of string anchors (^ and $) to match an exact value. func (r *Route) HeadersRegexp(pairs ...string) *Route { if r.err == nil { var headers map[string]*regexp.Regexp headers, r.err = mapFromPairsToRegex(pairs...) return r.addMatcher(headerRegexMatcher(headers)) } return r } // Host ----------------------------------------------------------------------- // Host adds a matcher for the URL host. // It accepts a template with zero or more URL variables enclosed by {}. // Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next dot. // // - {name:pattern} matches the given regexp pattern. // // For example: // // r := mux.NewRouter().NewRoute() // r.Host("www.example.com") // r.Host("{subdomain}.domain.com") // r.Host("{subdomain:[a-z]+}.domain.com") // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Host(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, regexpTypeHost) return r } // MatcherFunc ---------------------------------------------------------------- // MatcherFunc is the function signature used by custom matchers. type MatcherFunc func(*http.Request, *RouteMatch) bool // Match returns the match for a given request. func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { return m(r, match) } // MatcherFunc adds a custom function to be used as request matcher. func (r *Route) MatcherFunc(f MatcherFunc) *Route { return r.addMatcher(f) } // Methods -------------------------------------------------------------------- // methodMatcher matches the request against HTTP methods. type methodMatcher []string func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { return matchInArray(m, r.Method) } // Methods adds a matcher for HTTP methods. // It accepts a sequence of one or more methods to be matched, e.g.: // "GET", "POST", "PUT". func (r *Route) Methods(methods ...string) *Route { for k, v := range methods { methods[k] = strings.ToUpper(v) } return r.addMatcher(methodMatcher(methods)) } // Path ----------------------------------------------------------------------- // Path adds a matcher for the URL path. // It accepts a template with zero or more URL variables enclosed by {}. The // template must start with a "/". // Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next slash. // // - {name:pattern} matches the given regexp pattern. // // For example: // // r := mux.NewRouter().NewRoute() // r.Path("/products/").Handler(ProductsHandler) // r.Path("/products/{key}").Handler(ProductsHandler) // r.Path("/articles/{category}/{id:[0-9]+}"). // Handler(ArticleHandler) // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Path(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, regexpTypePath) return r } // PathPrefix ----------------------------------------------------------------- // PathPrefix adds a matcher for the URL path prefix. This matches if the given // template is a prefix of the full URL path. See Route.Path() for details on // the tpl argument. // // Note that it does not treat slashes specially ("/foobar/" will be matched by // the prefix "/foo") so you may want to use a trailing slash here. // // Also note that the setting of Router.StrictSlash() has no effect on routes // with a PathPrefix matcher. func (r *Route) PathPrefix(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) return r } // Query ---------------------------------------------------------------------- // Queries adds a matcher for URL query values. // It accepts a sequence of key/value pairs. Values may define variables. // For example: // // r := mux.NewRouter().NewRoute() // r.Queries("foo", "bar", "id", "{id:[0-9]+}") // // The above route will only match if the URL contains the defined queries // values, e.g.: ?foo=bar&id=42. // // If the value is an empty string, it will match any value if the key is set. // // Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next slash. // // - {name:pattern} matches the given regexp pattern. func (r *Route) Queries(pairs ...string) *Route { length := len(pairs) if length%2 != 0 { r.err = fmt.Errorf( "mux: number of parameters must be multiple of 2, got %v", pairs) return nil } for i := 0; i < length; i += 2 { if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { return r } } return r } // Schemes -------------------------------------------------------------------- // schemeMatcher matches the request against URL schemes. type schemeMatcher []string func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { scheme := r.URL.Scheme // https://golang.org/pkg/net/http/#Request // "For [most] server requests, fields other than Path and RawQuery will be // empty." // Since we're an http muxer, the scheme is either going to be http or https // though, so we can just set it based on the tls termination state. if scheme == "" { if r.TLS == nil { scheme = "http" } else { scheme = "https" } } return matchInArray(m, scheme) } // Schemes adds a matcher for URL schemes. // It accepts a sequence of schemes to be matched, e.g.: "http", "https". // If the request's URL has a scheme set, it will be matched against. // Generally, the URL scheme will only be set if a previous handler set it, // such as the ProxyHeaders handler from gorilla/handlers. // If unset, the scheme will be determined based on the request's TLS // termination state. // The first argument to Schemes will be used when constructing a route URL. func (r *Route) Schemes(schemes ...string) *Route { for k, v := range schemes { schemes[k] = strings.ToLower(v) } if len(schemes) > 0 { r.buildScheme = schemes[0] } return r.addMatcher(schemeMatcher(schemes)) } // BuildVarsFunc -------------------------------------------------------------- // BuildVarsFunc is the function signature used by custom build variable // functions (which can modify route variables before a route's URL is built). type BuildVarsFunc func(map[string]string) map[string]string // BuildVarsFunc adds a custom function to be used to modify build variables // before a route's URL is built. func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { if r.buildVarsFunc != nil { // compose the old and new functions old := r.buildVarsFunc r.buildVarsFunc = func(m map[string]string) map[string]string { return f(old(m)) } } else { r.buildVarsFunc = f } return r } // Subrouter ------------------------------------------------------------------ // Subrouter creates a subrouter for the route. // // It will test the inner routes only if the parent route matched. For example: // // r := mux.NewRouter().NewRoute() // s := r.Host("www.example.com").Subrouter() // s.HandleFunc("/products/", ProductsHandler) // s.HandleFunc("/products/{key}", ProductHandler) // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) // // Here, the routes registered in the subrouter won't be tested if the host // doesn't match. func (r *Route) Subrouter() *Router { // initialize a subrouter with a copy of the parent route's configuration router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} r.addMatcher(router) return router } // ---------------------------------------------------------------------------- // URL building // ---------------------------------------------------------------------------- // URL builds a URL for the route. // // It accepts a sequence of key/value pairs for the route variables. For // example, given this route: // // r := mux.NewRouter() // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). // Name("article") // // ...a URL for it can be built using: // // url, err := r.Get("article").URL("category", "technology", "id", "42") // // ...which will return an url.URL with the following path: // // "/articles/technology/42" // // This also works for host variables: // // r := mux.NewRouter() // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). // Host("{subdomain}.domain.com"). // Name("article") // // // url.String() will be "http://news.domain.com/articles/technology/42" // url, err := r.Get("article").URL("subdomain", "news", // "category", "technology", // "id", "42") // // The scheme of the resulting url will be the first argument that was passed to Schemes: // // // url.String() will be "https://example.com" // r := mux.NewRouter().NewRoute() // url, err := r.Host("example.com") // .Schemes("https", "http").URL() // // All variables defined in the route are required, and their values must // conform to the corresponding patterns. func (r *Route) URL(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } values, err := r.prepareVars(pairs...) if err != nil { return nil, err } var scheme, host, path string queries := make([]string, 0, len(r.regexp.queries)) if r.regexp.host != nil { if host, err = r.regexp.host.url(values); err != nil { return nil, err } scheme = "http" if r.buildScheme != "" { scheme = r.buildScheme } } if r.regexp.path != nil { if path, err = r.regexp.path.url(values); err != nil { return nil, err } } for _, q := range r.regexp.queries { var query string if query, err = q.url(values); err != nil { return nil, err } queries = append(queries, query) } return &url.URL{ Scheme: scheme, Host: host, Path: path, RawQuery: strings.Join(queries, "&"), }, nil } // URLHost builds the host part of the URL for a route. See Route.URL(). // // The route must have a host defined. func (r *Route) URLHost(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } if r.regexp.host == nil { return nil, errors.New("mux: route doesn't have a host") } values, err := r.prepareVars(pairs...) if err != nil { return nil, err } host, err := r.regexp.host.url(values) if err != nil { return nil, err } u := &url.URL{ Scheme: "http", Host: host, } if r.buildScheme != "" { u.Scheme = r.buildScheme } return u, nil } // URLPath builds the path part of the URL for a route. See Route.URL(). // // The route must have a path defined. func (r *Route) URLPath(pairs ...string) (*url.URL, error) { if r.err != nil { return nil, r.err } if r.regexp.path == nil { return nil, errors.New("mux: route doesn't have a path") } values, err := r.prepareVars(pairs...) if err != nil { return nil, err } path, err := r.regexp.path.url(values) if err != nil { return nil, err } return &url.URL{ Path: path, }, nil } // GetPathTemplate returns the template used to build the // route match. // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if the route does not define a path. func (r *Route) GetPathTemplate() (string, error) { if r.err != nil { return "", r.err } if r.regexp.path == nil { return "", errors.New("mux: route doesn't have a path") } return r.regexp.path.template, nil } // GetPathRegexp returns the expanded regular expression used to match route path. // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if the route does not define a path. func (r *Route) GetPathRegexp() (string, error) { if r.err != nil { return "", r.err } if r.regexp.path == nil { return "", errors.New("mux: route does not have a path") } return r.regexp.path.regexp.String(), nil } // GetQueriesRegexp returns the expanded regular expressions used to match the // route queries. // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if the route does not have queries. func (r *Route) GetQueriesRegexp() ([]string, error) { if r.err != nil { return nil, r.err } if r.regexp.queries == nil { return nil, errors.New("mux: route doesn't have queries") } queries := make([]string, 0, len(r.regexp.queries)) for _, query := range r.regexp.queries { queries = append(queries, query.regexp.String()) } return queries, nil } // GetQueriesTemplates returns the templates used to build the // query matching. // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if the route does not define queries. func (r *Route) GetQueriesTemplates() ([]string, error) { if r.err != nil { return nil, r.err } if r.regexp.queries == nil { return nil, errors.New("mux: route doesn't have queries") } queries := make([]string, 0, len(r.regexp.queries)) for _, query := range r.regexp.queries { queries = append(queries, query.template) } return queries, nil } // GetMethods returns the methods the route matches against // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if route does not have methods. func (r *Route) GetMethods() ([]string, error) { if r.err != nil { return nil, r.err } for _, m := range r.matchers { if methods, ok := m.(methodMatcher); ok { return []string(methods), nil } } return nil, errors.New("mux: route doesn't have methods") } // GetHostTemplate returns the template used to build the // route match. // This is useful for building simple REST API documentation and for instrumentation // against third-party services. // An error will be returned if the route does not define a host. func (r *Route) GetHostTemplate() (string, error) { if r.err != nil { return "", r.err } if r.regexp.host == nil { return "", errors.New("mux: route doesn't have a host") } return r.regexp.host.template, nil } // GetVarNames returns the names of all variables added by regexp matchers // These can be used to know which route variables should be passed into r.URL() func (r *Route) GetVarNames() ([]string, error) { if r.err != nil { return nil, r.err } var varNames []string if r.regexp.host != nil { varNames = append(varNames, r.regexp.host.varsN...) } if r.regexp.path != nil { varNames = append(varNames, r.regexp.path.varsN...) } for _, regx := range r.regexp.queries { varNames = append(varNames, regx.varsN...) } return varNames, nil } // prepareVars converts the route variable pairs into a map. If the route has a // BuildVarsFunc, it is invoked. func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { m, err := mapFromPairsToString(pairs...) if err != nil { return nil, err } return r.buildVars(m), nil } func (r *Route) buildVars(m map[string]string) map[string]string { if r.buildVarsFunc != nil { m = r.buildVarsFunc(m) } return m } ================================================ FILE: examples/web/vendor/github.com/gorilla/mux/test_helpers.go ================================================ // Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package mux import "net/http" // SetURLVars sets the URL variables for the given request, to be accessed via // mux.Vars for testing route behaviour. Arguments are not modified, a shallow // copy is returned. // // This API should only be used for testing purposes; it provides a way to // inject variables into the request context. Alternatively, URL variables // can be set by making a route that captures the required variables, // starting a server and sending the request to that server. func SetURLVars(r *http.Request, val map[string]string) *http.Request { return requestWithVars(r, val) } ================================================ FILE: examples/web/vendor/github.com/xyproto/pinterface/.gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ================================================ FILE: examples/web/vendor/github.com/xyproto/pinterface/.travis.yml ================================================ language: go go: - "1.8" - "1.9" - "1.10" - "1.11" - "1.12" - "1.13" - "1.14" - tip ================================================ FILE: examples/web/vendor/github.com/xyproto/pinterface/LICENSE ================================================ Copyright 2021 Alexander F. Rødseth Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: examples/web/vendor/github.com/xyproto/pinterface/README.md ================================================ # pinterface [![Go Report Card](https://goreportcard.com/badge/github.com/xyproto/pinterface)](https://goreportcard.com/report/github.com/xyproto/pinterface) [![License](https://img.shields.io/badge/license-BSD-green.svg?style=flat)](https://raw.githubusercontent.com/xyproto/pinterface/main/LICENSE) Interface types for `simple*` and `permission*` packages. Interfaces for: --------------- * Redis: [permissions2](https://github.com/xyproto/permissions2) and [simpleredis](https://github.com/xyproto/simpleredis) * Bolt: [permissionbolt](https://github.com/xyproto/permissionbolt) and [simplebolt](https://github.com/xyproto/simplebolt) * MariaDB/MySQL: [permissionsql](https://github.com/xyproto/permissionsql) and [simplemaria](https://github.com/xyproto/simplemaria) * PostgreSQL: [pstore](https://github.com/xyproto/pstore) and [simplehstore](https://github.com/xyproto/simplehstore) [![Packaging status](https://repology.org/badge/vertical-allrepos/go:github-xyproto-pinterface.svg)](https://repology.org/project/go:github-xyproto-pinterface/versions) General information ------------------- * Version: 1.5.3 (The tag is `v1.5.3` to work better with `go mod`. The API has version `5.3`.) * License: BSD-3 * Author: Alexander F. Rødseth <xyproto@archlinux.org> ================================================ FILE: examples/web/vendor/github.com/xyproto/pinterface/pinterface.go ================================================ // Package pinterface provides interface types for the xyproto/simple* and xyproto/permission* packages package pinterface import "net/http" // Version is the API version. The API is stable within the same major version number const Version = 5.3 // Database interfaces type IList interface { Add(value string) error All() ([]string, error) Clear() error LastN(n int) ([]string, error) Last() (string, error) Remove() error } type ISet interface { Add(value string) error All() ([]string, error) Clear() error Del(value string) error Has(value string) (bool, error) Remove() error } type IHashMap interface { All() ([]string, error) Clear() error DelKey(owner, key string) error Del(key string) error Exists(owner string) (bool, error) Get(owner, key string) (string, error) Has(owner, key string) (bool, error) Keys(owner string) ([]string, error) Remove() error Set(owner, key, value string) error } type IHashMap2 interface { All() ([]string, error) AllWhere(key, value string) ([]string, error) Clear() error Count() (int64, error) DelKey(owner, key string) error Del(key string) error Empty() (bool, error) Exists(owner string) (bool, error) GetMap(owner string, keys []string) (map[string]string, error) Get(owner, key string) (string, error) Has(owner, key string) (bool, error) Keys(owner string) ([]string, error) Remove() error SetLargeMap(all map[string]map[string]string) error SetMap(owner string, m map[string]string) error Set(owner, key, value string) error } type IKeyValue interface { Clear() error Del(key string) error Get(key string) (string, error) Inc(key string) (string, error) Remove() error Set(key, value string) error } // Interface for making it possible to depend on different versions of the permission package, // or other packages that implement userstates. type IUserState interface { AddUnconfirmed(username, confirmationCode string) AddUser(username, password, email string) AdminRights(req *http.Request) bool AllUnconfirmedUsernames() ([]string, error) AllUsernames() ([]string, error) AlreadyHasConfirmationCode(confirmationCode string) bool BooleanField(username, fieldname string) bool ClearCookie(w http.ResponseWriter) ConfirmationCode(username string) (string, error) ConfirmUserByConfirmationCode(confirmationcode string) error Confirm(username string) CookieSecret() string CookieTimeout(username string) int64 CorrectPassword(username, password string) bool Email(username string) (string, error) FindUserByConfirmationCode(confirmationcode string) (string, error) GenerateUniqueConfirmationCode() (string, error) HashPassword(username, password string) string HasUser(username string) bool IsAdmin(username string) bool IsConfirmed(username string) bool IsLoggedIn(username string) bool Login(w http.ResponseWriter, username string) error Logout(username string) MarkConfirmed(username string) PasswordAlgo() string PasswordHash(username string) (string, error) RemoveAdminStatus(username string) RemoveUnconfirmed(username string) RemoveUser(username string) SetAdminStatus(username string) SetBooleanField(username, fieldname string, val bool) SetCookieSecret(cookieSecret string) SetCookieTimeout(cookieTime int64) SetLoggedIn(username string) SetLoggedOut(username string) SetMinimumConfirmationCodeLength(length int) SetPasswordAlgo(algorithm string) error SetPassword(username, password string) SetUsernameCookie(w http.ResponseWriter, username string) error UsernameCookie(req *http.Request) (string, error) Username(req *http.Request) string UserRights(req *http.Request) bool Creator() ICreator Host() IHost Users() IHashMap } // Data structure creator type ICreator interface { NewHashMap(id string) (IHashMap, error) NewKeyValue(id string) (IKeyValue, error) NewList(id string) (IList, error) NewSet(id string) (ISet, error) } // Database host (or file) type IHost interface { Close() Ping() error } // Redis host (implemented structures can also be an IHost, of course) type IRedisHost interface { DatabaseIndex() Pool() } // Redis data structure creator type IRedisCreator interface { SelectDatabase(dbindex int) } // Middleware for permissions type IPermissions interface { AddAdminPath(prefix string) AddPublicPath(prefix string) AddUserPath(prefix string) Clear() DenyFunction() http.HandlerFunc Rejected(w http.ResponseWriter, req *http.Request) bool ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) SetAdminPath(pathPrefixes []string) SetDenyFunction(f http.HandlerFunc) SetPublicPath(pathPrefixes []string) SetUserPath(pathPrefixes []string) UserState() IUserState } ================================================ FILE: examples/web/vendor/github.com/xyproto/simpleredis/v2/LICENSE ================================================ Copyright 2023 Alexander F. Rødseth Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: examples/web/vendor/github.com/xyproto/simpleredis/v2/creator.go ================================================ package simpleredis import ( "github.com/xyproto/pinterface" ) // For implementing pinterface.ICreator type RedisCreator struct { pool *ConnectionPool dbindex int } func NewCreator(pool *ConnectionPool, dbindex int) *RedisCreator { return &RedisCreator{pool, dbindex} } func (c *RedisCreator) SelectDatabase(dbindex int) { c.dbindex = dbindex } func (c *RedisCreator) NewList(id string) (pinterface.IList, error) { return &List{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewSet(id string) (pinterface.ISet, error) { return &Set{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewHashMap(id string) (pinterface.IHashMap, error) { return &HashMap{c.pool, id, c.dbindex}, nil } func (c *RedisCreator) NewKeyValue(id string) (pinterface.IKeyValue, error) { return &KeyValue{c.pool, id, c.dbindex}, nil } ================================================ FILE: examples/web/vendor/github.com/xyproto/simpleredis/v2/simpleredis.go ================================================ // Package simpleredis provides an easy way to use Redis. package simpleredis import ( "errors" "strconv" "strings" "time" "github.com/gomodule/redigo/redis" ) const ( // Version number. Stable API within major version numbers. Version = 2.6 // The default [url]:port that Redis is running at defaultRedisServer = ":6379" ) // Common for each of the Redis data structures used here type redisDatastructure struct { pool *ConnectionPool id string dbindex int } type ( // A pool of readily available Redis connections ConnectionPool redis.Pool List redisDatastructure Set redisDatastructure HashMap redisDatastructure KeyValue redisDatastructure ) var ( // Timeout settings for new connections connectTimeout = 7 * time.Second readTimeout = 7 * time.Second writeTimeout = 7 * time.Second idleTimeout = 240 * time.Second // How many connections should stay ready for requests, at a maximum? // When an idle connection is used, new idle connections are created. maxIdleConnections = 3 ) /* --- Helper functions --- */ // Connect to the local instance of Redis at port 6379 func newRedisConnection() (redis.Conn, error) { return newRedisConnectionTo(defaultRedisServer) } // Connect to host:port, host may be omitted, so ":6379" is valid. // Will not try to AUTH with any given password (password@host:port). func newRedisConnectionTo(hostColonPort string) (redis.Conn, error) { // Discard the password, if provided if _, theRest, ok := twoFields(hostColonPort, "@"); ok { hostColonPort = theRest } hostColonPort = strings.TrimSpace(hostColonPort) c, err := redis.Dial("tcp", hostColonPort, redis.DialConnectTimeout(connectTimeout), redis.DialReadTimeout(readTimeout), redis.DialWriteTimeout(writeTimeout)) if err != nil { if c != nil { c.Close() } return nil, err } return c, nil } // Get a string from a list of results at a given position func getString(bi []interface{}, i int) string { return string(bi[i].([]uint8)) } // Test if the local Redis server is up and running func TestConnection() (err error) { return TestConnectionHost(defaultRedisServer) } // Test if a given Redis server at host:port is up and running. // Does not try to PING or AUTH. func TestConnectionHost(hostColonPort string) (err error) { // Connect to the given host:port conn, err := newRedisConnectionTo(hostColonPort) defer func() { if conn != nil { conn.Close() } if r := recover(); r != nil { err = errors.New("Could not connect to redis server: " + hostColonPort) } }() return err } /* --- ConnectionPool functions --- */ func copyPoolValues(src *redis.Pool) ConnectionPool { return ConnectionPool{ Dial: src.Dial, DialContext: src.DialContext, TestOnBorrow: src.TestOnBorrow, MaxIdle: src.MaxIdle, MaxActive: src.MaxActive, IdleTimeout: src.IdleTimeout, Wait: src.Wait, MaxConnLifetime: src.MaxConnLifetime, } } // Create a new connection pool func NewConnectionPool() *ConnectionPool { // The second argument is the maximum number of idle connections redisPool := &redis.Pool{ MaxIdle: maxIdleConnections, IdleTimeout: idleTimeout, Dial: newRedisConnection, } pool := copyPoolValues(redisPool) return &pool } // Split a string into two parts, given a delimiter. // Returns the two parts and true if it works out. func twoFields(s, delim string) (string, string, bool) { if strings.Count(s, delim) != 1 { return s, "", false } fields := strings.Split(s, delim) return fields[0], fields[1], true } // Create a new connection pool given a host:port string. // A password may be supplied as well, on the form "password@host:port". func NewConnectionPoolHost(hostColonPort string) *ConnectionPool { // Create a redis Pool redisPool := &redis.Pool{ // Maximum number of idle connections to the redis database MaxIdle: maxIdleConnections, IdleTimeout: idleTimeout, // Anonymous function for calling new RedisConnectionTo with the host:port Dial: func() (redis.Conn, error) { conn, err := newRedisConnectionTo(hostColonPort) if err != nil { return nil, err } // If a password is given, use it to authenticate if password, _, ok := twoFields(hostColonPort, "@"); ok { if password != "" { if _, err := conn.Do("AUTH", password); err != nil { conn.Close() return nil, err } } } return conn, err }, } pool := copyPoolValues(redisPool) return &pool } // Set the number of maximum *idle* connections standing ready when // creating new connection pools. When an idle connection is used, // a new idle connection is created. The default is 3 and should be fine // for most cases. func SetMaxIdleConnections(maximum int) { maxIdleConnections = maximum } // Get one of the available connections from the connection pool, given a database index func (pool *ConnectionPool) Get(dbindex int) redis.Conn { redisPool := (*redis.Pool)(pool) conn := redisPool.Get() // The default database index is 0 if dbindex != 0 { // SELECT is not critical, ignore the return values conn.Do("SELECT", strconv.Itoa(dbindex)) } return conn } // Ping the server by sending a PING command func (pool *ConnectionPool) Ping() error { redisPool := (*redis.Pool)(pool) conn := redisPool.Get() _, err := conn.Do("PING") return err } // Close down the connection pool func (pool *ConnectionPool) Close() { redisPool := (*redis.Pool)(pool) redisPool.Close() } /* --- List functions --- */ // Create a new list func NewList(pool *ConnectionPool, id string) *List { return &List{pool, id, 0} } // Select a different database func (rl *List) SelectDatabase(dbindex int) { rl.dbindex = dbindex } // Returns the element at index index in the list func (rl *List) Get(index int64) (string, error) { conn := rl.pool.Get(rl.dbindex) result, err := conn.Do("LINDEX", rl.id, index) if err != nil { panic(err) } return redis.String(result, err) } // Get the size of the list func (rl *List) Size() (int64, error) { conn := rl.pool.Get(rl.dbindex) size, err := conn.Do("LLEN", rl.id) if err != nil { panic(err) } return redis.Int64(size, err) } // Removes and returns the first element of the list func (rl *List) PopFirst() (string, error) { conn := rl.pool.Get(rl.dbindex) result, err := conn.Do("LPOP", rl.id) if err != nil { panic(err) } return redis.String(result, err) } // Removes and returns the last element of the list func (rl *List) PopLast() (string, error) { conn := rl.pool.Get(rl.dbindex) result, err := conn.Do("LPOP", rl.id) if err != nil { panic(err) } return redis.String(result, err) } // Add an element to the start of the list func (rl *List) AddStart(value string) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("RPUSH", rl.id, value) return err } // Add an element to the end of the list list func (rl *List) AddEnd(value string) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("LPUSH", rl.id, value) return err } // Default Add, aliased to List.AddStart func (rl *List) Add(value string) error { return rl.AddStart(value) } // Get all elements of a list func (rl *List) All() ([]string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "0", "-1")) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Deprecated func (rl *List) GetAll() ([]string, error) { return rl.All() } // Get the last element of a list func (rl *List) Last() (string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "-1", "-1")) if len(result) == 1 { return getString(result, 0), err } return "", err } // Deprecated func (rl *List) GetLast() (string, error) { return rl.Last() } // Get the last N elements of a list func (rl *List) LastN(n int) ([]string, error) { conn := rl.pool.Get(rl.dbindex) result, err := redis.Values(conn.Do("LRANGE", rl.id, "-"+strconv.Itoa(n), "-1")) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Deprecated func (rl *List) GetLastN(n int) ([]string, error) { return rl.LastN(n) } // Remove the first occurrence of an element from the list func (rl *List) RemoveElement(value string) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("LREM", rl.id, value) return err } // Set element of list at index n to value func (rl *List) Set(index int64, value string) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("LSET", rl.id, index, value) return err } // Trim an existing list so that it will contain only the specified range of // elements specified. func (rl *List) Trim(start, stop int64) error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("LTRIM", rl.id, start, stop) return err } // Remove this list func (rl *List) Remove() error { conn := rl.pool.Get(rl.dbindex) _, err := conn.Do("DEL", rl.id) return err } // Clear the contents func (rl *List) Clear() error { return rl.Remove() } /* --- Set functions --- */ // Create a new set func NewSet(pool *ConnectionPool, id string) *Set { return &Set{pool, id, 0} } // Select a different database func (rs *Set) SelectDatabase(dbindex int) { rs.dbindex = dbindex } // Add an element to the set func (rs *Set) Add(value string) error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("SADD", rs.id, value) return err } // Returns the set cardinality (number of elements) of the set func (rs *Set) Size() (int64, error) { conn := rs.pool.Get(rs.dbindex) size, err := conn.Do("SCARD", rs.id) if err != nil { panic(err) } return redis.Int64(size, err) } // Check if a given value is in the set func (rs *Set) Has(value string) (bool, error) { conn := rs.pool.Get(rs.dbindex) retval, err := conn.Do("SISMEMBER", rs.id, value) if err != nil { panic(err) } return redis.Bool(retval, err) } // Get all elements of the set func (rs *Set) All() ([]string, error) { conn := rs.pool.Get(rs.dbindex) result, err := redis.Values(conn.Do("SMEMBERS", rs.id)) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Deprecated func (rs *Set) GetAll() ([]string, error) { return rs.All() } // Remove a random member from the set func (rs *Set) Pop() (string, error) { conn := rs.pool.Get(rs.dbindex) result, err := conn.Do("SPOP", rs.id) if err != nil { panic(err) } return redis.String(result, err) } // Get a random member of the set func (rs *Set) Random() (string, error) { conn := rs.pool.Get(rs.dbindex) result, err := conn.Do("SRANDMEMBER", rs.id) if err != nil { panic(err) } return redis.String(result, err) } // Remove an element from the set func (rs *Set) Del(value string) error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("SREM", rs.id, value) return err } // Remove this set func (rs *Set) Remove() error { conn := rs.pool.Get(rs.dbindex) _, err := conn.Do("DEL", rs.id) return err } // Clear the contents func (rs *Set) Clear() error { return rs.Remove() } /* --- HashMap functions --- */ // Create a new hashmap func NewHashMap(pool *ConnectionPool, id string) *HashMap { return &HashMap{pool, id, 0} } // Select a different database func (rh *HashMap) SelectDatabase(dbindex int) { rh.dbindex = dbindex } // Set a value in a hashmap given the element id (for instance a user id) and the key (for instance "password") func (rh *HashMap) Set(elementid, key, value string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("HSET", rh.id+":"+elementid, key, value) return err } // Given an element id, set a key and a value together with an expiration time func (rh *HashMap) SetExpire(elementid, key, value string, expire time.Duration) error { conn := rh.pool.Get(rh.dbindex) if _, err := conn.Do("HSET", rh.id+":"+elementid, key, value); err != nil { return err } // No EXPIRE in Redis for hash keys, as far as I can tell from the documentation. // This is the manual way. go func() { time.Sleep(expire) rh.DelKey(elementid, key) }() // Set the elementid to expire in the given duration (as milliseconds) //expireMilliseconds := expire.Nanoseconds() / 1000000 //if _, err := conn.Do("PEXPIRE", rh.id+":"+elementid, expireMilliseconds); err != nil { // return err //} return nil } // Commented out because this would only return TTL for the elementid, not for the key // TimeToLive returns how long a key has to live until it expires // Returns a duration of 0 when the time has passed //func (rh *HashMap) TimeToLive(elementid string) (time.Duration, error) { // conn := rh.pool.Get(rh.dbindex) // ttlSecondsInterface, err := conn.Do("TTL", rh.id+":"+elementid) // if err != nil || ttlSecondsInterface.(int64) <= 0 { // return time.Duration(0), err // } // ns := time.Duration(ttlSecondsInterface.(int64)) * time.Second // return ns, nil //} // Get a value from a hashmap given the element id (for instance a user id) and the key (for instance "password") func (rh *HashMap) Get(elementid, key string) (string, error) { conn := rh.pool.Get(rh.dbindex) result, err := redis.String(conn.Do("HGET", rh.id+":"+elementid, key)) if err != nil { return "", err } return result, nil } // Check if a given elementid + key is in the hash map func (rh *HashMap) Has(elementid, key string) (bool, error) { conn := rh.pool.Get(rh.dbindex) retval, err := conn.Do("HEXISTS", rh.id+":"+elementid, key) if err != nil { panic(err) } return redis.Bool(retval, err) } // Keys returns the keys of the given elementid. func (rh *HashMap) Keys(elementid string) ([]string, error) { conn := rh.pool.Get(rh.dbindex) result, err := redis.Values(conn.Do("HKEYS", rh.id+":"+elementid)) strs := make([]string, len(result)) for i := 0; i < len(result); i++ { strs[i] = getString(result, i) } return strs, err } // Check if a given elementid exists as a hash map at all func (rh *HashMap) Exists(elementid string) (bool, error) { // TODO: key is not meant to be a wildcard, check for "*" return hasKey(rh.pool, rh.id+":"+elementid, rh.dbindex) } // Get all elementid's for all hash elements func (rh *HashMap) All() ([]string, error) { conn := rh.pool.Get(rh.dbindex) result, err := redis.Values(conn.Do("KEYS", rh.id+":*")) strs := make([]string, len(result)) idlen := len(rh.id) for i := 0; i < len(result); i++ { strs[i] = getString(result, i)[idlen+1:] } return strs, err } // Deprecated func (rh *HashMap) GetAll() ([]string, error) { return rh.All() } // Remove a key for an entry in a hashmap (for instance the email field for a user) func (rh *HashMap) DelKey(elementid, key string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("HDEL", rh.id+":"+elementid, key) return err } // Remove an element (for instance a user) func (rh *HashMap) Del(elementid string) error { conn := rh.pool.Get(rh.dbindex) _, err := conn.Do("DEL", rh.id+":"+elementid) return err } // Remove this hashmap (all keys that starts with this hashmap id and a colon) func (rh *HashMap) Remove() error { conn := rh.pool.Get(rh.dbindex) // Find all hashmap keys that starts with rh.id+":" results, err := redis.Values(conn.Do("KEYS", rh.id+":*")) if err != nil { return err } // For each key id for i := 0; i < len(results); i++ { // Delete this key if _, err = conn.Do("DEL", getString(results, i)); err != nil { return err } } return nil } // Clear the contents func (rh *HashMap) Clear() error { return rh.Remove() } /* --- KeyValue functions --- */ // Create a new key/value func NewKeyValue(pool *ConnectionPool, id string) *KeyValue { return &KeyValue{pool, id, 0} } // Select a different database func (rkv *KeyValue) SelectDatabase(dbindex int) { rkv.dbindex = dbindex } // Set a key and value func (rkv *KeyValue) Set(key, value string) error { conn := rkv.pool.Get(rkv.dbindex) _, err := conn.Do("SET", rkv.id+":"+key, value) return err } // Set a key and value, with expiry func (rkv *KeyValue) SetExpire(key, value string, expire time.Duration) error { conn := rkv.pool.Get(rkv.dbindex) // Convert from nanoseconds to milliseconds expireMilliseconds := expire.Nanoseconds() / 1000000 // Set the value, together with an expiry time, given in milliseconds _, err := conn.Do("SET", rkv.id+":"+key, value, "PX", expireMilliseconds) return err } // TimeToLive returns how long a key has to live until it expires // Returns a duration of 0 when the time has passed func (rkv *KeyValue) TimeToLive(key string) (time.Duration, error) { conn := rkv.pool.Get(rkv.dbindex) ttlSecondsInterface, err := conn.Do("TTL", rkv.id+":"+key) if err != nil || ttlSecondsInterface.(int64) <= 0 { return time.Duration(0), err } ns := time.Duration(ttlSecondsInterface.(int64)) * time.Second return ns, nil } // Get a value given a key func (rkv *KeyValue) Get(key string) (string, error) { conn := rkv.pool.Get(rkv.dbindex) result, err := redis.String(conn.Do("GET", rkv.id+":"+key)) if err != nil { return "", err } return result, nil } // Remove a key func (rkv *KeyValue) Del(key string) error { conn := rkv.pool.Get(rkv.dbindex) _, err := conn.Do("DEL", rkv.id+":"+key) return err } // Increase the value of a key, returns the new value // Returns an empty string if there were errors, // or "0" if the key does not already exist. func (rkv *KeyValue) Inc(key string) (string, error) { conn := rkv.pool.Get(rkv.dbindex) result, err := redis.Int64(conn.Do("INCR", rkv.id+":"+key)) if err != nil { return "0", err } return strconv.FormatInt(result, 10), nil } // Remove this key/value func (rkv *KeyValue) Remove() error { conn := rkv.pool.Get(rkv.dbindex) // Find all keys that starts with rkv.id+":" results, err := redis.Values(conn.Do("KEYS", rkv.id+":*")) if err != nil { return err } // For each key id for i := 0; i < len(results); i++ { // Delete this key if _, err = conn.Do("DEL", getString(results, i)); err != nil { return err } } return nil } // Clear the contents func (rkv *KeyValue) Clear() error { return rkv.Remove() } // --- Generic redis functions --- // Check if a key exists. The key can be a wildcard (ie. "user*"). func hasKey(pool *ConnectionPool, wildcard string, dbindex int) (bool, error) { conn := pool.Get(dbindex) result, err := redis.Values(conn.Do("KEYS", wildcard)) if err != nil { return false, err } return len(result) > 0, nil } // --- Related to setting and retrieving timeout values // SetConnectTimeout sets the connect timeout for new connections func SetConnectTimeout(t time.Duration) { connectTimeout = t } // SetReadTimeout sets the read timeout for new connections func SetReadTimeout(t time.Duration) { readTimeout = t } // SetWriteTimeout sets the write timeout for new connections func SetWriteTimeout(t time.Duration) { writeTimeout = t } // SetIdleTimeout sets the idle timeout for new connections func SetIdleTimeout(t time.Duration) { idleTimeout = t } // ConnectTimeout returns the current connect timeout for new connections func ConnectTimeout() time.Duration { return connectTimeout } // ReadTimeout returns the current read timeout for new connections func ReadTimeout() time.Duration { return readTimeout } // WriteTimeout returns the current write timeout for new connections func WriteTimeout() time.Duration { return writeTimeout } // IdleTimeout returns the current idle timeout for new connections func IdleTimeout() time.Duration { return idleTimeout } ================================================ FILE: examples/web/vendor/modules.txt ================================================ # github.com/codegangsta/negroni v1.0.0 ## explicit github.com/codegangsta/negroni # github.com/gomodule/redigo v1.8.9 ## explicit; go 1.16 github.com/gomodule/redigo/redis # github.com/gorilla/mux v1.8.1 ## explicit; go 1.20 github.com/gorilla/mux # github.com/xyproto/pinterface v1.5.3 ## explicit; go 1.8 github.com/xyproto/pinterface # github.com/xyproto/simpleredis/v2 v2.6.5 ## explicit; go 1.17 github.com/xyproto/simpleredis/v2 ================================================ FILE: go.mod ================================================ module github.com/kubernetes/kompose go 1.24.0 replace github.com/openshift/api v3.9.0+incompatible => github.com/openshift/api v0.0.0-20230704153349-abb98ff04d03 require ( github.com/compose-spec/compose-go/v2 v2.10.0 github.com/deckarep/golang-set v1.8.0 github.com/fatih/structs v1.1.0 github.com/fsouza/go-dockerclient v1.12.3 github.com/google/go-cmp v0.7.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/joho/godotenv v1.5.1 github.com/mattn/go-shellwords v1.0.12 github.com/novln/docker-parser v1.0.0 github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.18.2 golang.org/x/tools/godoc v0.1.0-deprecated gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.2 k8s.io/api v0.31.2 k8s.io/apimachinery v0.31.2 ) require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) ================================================ FILE: go.sum ================================================ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/compose-spec/compose-go/v2 v2.10.0 h1:K2C5LQ3KXvkYpy5N/SG6kIYB90iiAirA9btoTh/gB0Y= github.com/compose-spec/compose-go/v2 v2.10.0/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/go-dockerclient v1.12.3 h1:CEsX4/msyMEekHAR9Pf8XniZBtwGo0Kl+mLPQ/AnSys= github.com/fsouza/go-dockerclient v1.12.3/go.mod h1:gl0t2KUfrsLbm4tw5/ySsJkkFpi7Fz9gXzY2BKLEvZA= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/novln/docker-parser v1.0.0 h1:PjEBd9QnKixcWczNGyEdfUrP6GR0YUilAqG7Wksg3uc= github.com/novln/docker-parser v1.0.0/go.mod h1:oCeM32fsoUwkwByB5wVjsrsVQySzPWkl3JdlTn1txpE= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/openshift/api v0.0.0-20230704153349-abb98ff04d03 h1:qMeI0T0VAk3ydumBleYCbR7P2clyyEophp+dBCtCrJA= github.com/openshift/api v0.0.0-20230704153349-abb98ff04d03/go.mod h1:yimSGmjsI+XF1mr+AKBs2//fSXIOhhetHGbMlBEfXbs= github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= ================================================ FILE: gover.coverprofile ================================================ mode: atomic github.com/kubernetes/kompose/pkg/loader/compose/compose.go:44.68,82.102 3 2 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:87.2,87.43 1 2 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:91.2,91.68 1 2 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:142.2,142.18 1 2 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:82.102,84.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:87.43,89.3 1 1 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:91.68,96.32 3 3 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:96.32,98.69 1 174 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:98.69,99.38 1 69 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:99.38,102.75 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:109.6,110.32 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:118.6,118.108 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:136.6,137.37 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:102.75,103.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:103.27,105.16 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:110.32,112.108 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:112.108,114.16 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:118.108,121.45 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:131.7,131.36 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:121.45,122.76 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:122.76,125.82 3 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:125.82,127.15 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:131.36,132.16 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:146.75,150.29 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:163.2,166.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:150.29,152.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:157.3,157.67 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:160.3,160.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:152.17,154.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:157.67,159.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:169.48,171.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:174.3,174.28 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:176.74,178.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:181.3,181.28 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:182.10,183.133 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:171.17,173.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:178.17,180.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:187.54,194.16 4 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:198.2,199.16 2 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:203.2,203.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:194.16,196.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/compose.go:199.16,201.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:81.52,83.27 2 8 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:120.2,120.13 1 8 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:83.27,87.10 4 8 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:102.3,102.22 1 8 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:88.41,89.18 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:90.41,91.19 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:92.41,93.19 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:94.41,95.27 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:95.27,97.5 1 1 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:97.10,99.5 1 1 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:102.22,107.4 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:107.9,110.23 2 6 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:113.4,116.6 1 6 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:110.23,112.5 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:125.61,127.40 2 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:134.2,134.37 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:127.40,129.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:132.3,132.48 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:129.17,131.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:137.60,138.38 1 7 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:139.23,140.47 1 3 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:141.18,142.46 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:143.22,144.50 1 2 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:145.18,146.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:147.10,148.132 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:152.53,154.2 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:156.51,159.2 2 3 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:161.46,163.2 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:165.60,169.16 4 4 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:172.2,173.20 2 4 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:169.16,171.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:177.48,178.21 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:186.2,186.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:178.21,179.23 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:184.3,184.24 1 0 github.com/kubernetes/kompose/pkg/loader/compose/utils.go:179.23,183.4 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:43.63,48.35 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:52.2,52.38 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:68.2,70.16 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:74.2,75.36 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:80.2,81.16 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:85.2,85.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:48.35,50.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:52.38,54.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:57.3,64.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:54.17,56.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:70.16,72.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:75.36,77.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:81.16,83.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:90.81,94.34 3 9 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:142.2,142.23 1 9 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:147.2,147.19 1 9 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:166.2,166.16 1 9 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:94.34,97.55 2 11 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:104.3,105.17 2 11 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:112.3,112.26 1 11 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:119.3,120.21 2 11 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:123.3,125.27 2 11 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:97.55,98.72 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:101.4,101.21 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:98.72,100.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:105.17,107.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:112.26,113.25 1 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:113.25,116.5 2 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:120.21,122.4 1 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:125.27,127.18 2 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:130.4,130.27 1 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:127.18,129.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:130.27,137.5 1 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:142.23,145.3 1 26 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:147.19,148.31 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:148.31,151.40 3 2 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:157.4,157.34 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:151.40,155.5 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:157.34,162.5 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:170.96,179.77 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:286.2,288.27 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:179.77,186.52 7 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:189.3,201.47 10 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:206.3,207.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:210.3,214.42 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:224.3,224.89 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:228.3,229.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:234.3,251.48 15 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:256.3,256.43 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:273.3,274.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:277.3,280.42 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:186.52,188.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:201.47,203.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:207.17,209.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:214.42,215.64 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:215.64,218.5 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:224.89,226.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:229.17,231.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:251.48,254.4 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:256.43,257.55 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:257.55,258.66 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:258.66,259.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:259.33,261.21 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:264.7,264.49 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:267.7,267.82 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:261.21,263.8 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:264.49,266.8 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:274.17,276.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:280.42,282.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:292.57,293.49 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:293.49,296.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:300.3,302.44 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:296.17,298.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:306.74,307.73 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:310.2,310.12 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:307.73,309.3 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:314.112,316.62 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:363.2,363.8 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:316.62,318.76 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:318.76,321.18 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:324.4,326.18 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:330.4,330.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:347.4,347.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:321.18,323.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:326.18,328.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:330.29,333.11 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:344.5,344.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:333.11,335.24 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:342.6,342.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:335.24,338.7 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:338.12,341.7 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:347.29,349.38 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:349.38,353.6 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:356.8,359.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:359.17,361.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:367.75,368.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:373.2,373.13 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:368.29,369.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:369.32,371.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:377.78,381.30 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:394.2,394.21 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:381.30,384.17 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:387.3,391.31 5 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:384.17,386.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:398.85,399.26 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:404.2,404.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:399.26,400.39 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:400.39,402.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:408.51,410.26 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:417.2,417.22 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:410.26,412.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:415.3,415.40 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v1v2.go:412.17,414.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:44.52,47.24 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:55.2,55.20 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:47.24,49.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:52.3,53.24 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:49.32,51.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:61.61,67.16 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:72.2,73.16 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:77.2,78.29 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:122.2,123.36 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:128.2,129.16 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:133.2,133.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:67.16,69.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:73.16,75.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:78.29,81.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:86.3,87.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:92.3,109.17 4 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:112.3,112.20 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:81.17,83.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:87.17,89.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:109.17,111.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:112.20,114.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:114.9,116.18 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:116.18,118.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:123.36,125.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:129.16,131.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:136.67,145.42 3 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:170.2,170.42 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:179.2,179.25 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:145.42,147.36 2 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:150.3,151.17 2 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:156.3,157.17 2 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:162.3,162.24 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:147.36,149.4 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:151.17,153.12 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:157.17,159.12 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:162.24,164.4 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:164.9,164.34 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:164.34,166.4 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:170.42,173.17 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:177.3,177.77 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:173.17,175.12 2 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:183.61,184.21 1 5 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:194.2,195.31 2 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:185.23,186.39 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:187.39,188.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:189.10,190.53 1 5 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:190.53,192.4 1 4 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:202.66,204.30 2 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:218.2,218.17 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:204.30,208.23 2 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:212.3,212.19 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:216.3,216.33 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:208.23,210.4 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:212.19,214.4 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:223.84,228.29 3 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:242.2,242.19 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:264.2,264.21 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:228.29,240.3 2 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:242.19,243.31 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:243.31,246.40 3 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:252.4,252.33 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:255.4,260.6 1 2 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:246.40,250.5 3 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:252.33,253.13 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:270.82,276.33 5 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:313.2,313.19 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:324.2,334.8 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:276.33,277.14 1 16 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:278.36,279.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:280.33,281.22 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:284.40,285.20 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:286.40,287.34 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:288.36,289.33 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:290.37,292.18 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:295.4,295.37 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:296.36,298.18 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:301.4,301.36 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:302.36,303.33 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:304.40,306.18 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:309.4,309.40 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:281.22,283.5 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:292.18,294.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:298.18,300.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:306.18,308.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:313.19,314.24 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:319.3,319.49 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:314.24,317.4 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:319.49,321.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:340.117,346.39 4 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:354.2,354.40 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:362.2,362.39 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:366.2,366.43 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:374.2,374.36 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:378.2,378.33 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:390.2,399.8 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:346.39,348.17 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:351.3,351.35 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:348.17,350.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:354.40,356.17 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:359.3,359.36 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:356.17,358.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:362.39,364.3 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:366.43,368.17 2 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:371.3,371.39 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:368.17,370.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:374.36,376.3 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:378.33,379.14 1 3 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:380.39,381.20 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:382.39,383.34 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:384.35,385.33 1 1 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:402.96,413.62 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:539.2,541.27 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:413.62,438.50 22 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:442.3,444.81 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:450.3,455.91 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:464.3,465.25 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:474.3,475.55 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:478.3,478.48 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:484.3,484.50 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:489.3,491.54 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:498.3,521.89 9 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:526.3,526.42 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:530.3,532.56 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:536.3,536.76 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:438.50,440.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:444.81,446.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:455.91,458.18 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:458.18,460.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:465.25,467.27 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:467.27,469.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:475.55,477.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:478.48,481.4 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:484.50,486.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:491.54,493.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:521.89,523.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:526.42,528.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:532.56,534.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:546.61,547.19 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:551.2,552.20 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:556.2,556.16 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:547.19,549.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:552.20,554.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:559.131,560.45 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:560.45,561.66 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:561.66,563.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:564.8,566.50 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:566.50,571.21 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:574.4,574.66 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:571.21,573.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:579.110,580.66 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:612.2,612.12 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:580.66,589.58 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:600.3,600.64 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:589.58,592.67 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:592.67,594.19 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:597.5,597.52 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:594.19,596.6 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:600.64,603.73 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:603.73,605.19 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:608.5,608.64 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:605.19,607.6 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:615.106,620.60 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:620.60,622.19 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:632.3,632.69 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:622.19,624.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:624.9,626.10 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:626.10,628.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:628.10,629.13 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:637.95,642.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:646.2,646.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:670.2,670.79 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:674.2,674.101 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:678.2,678.68 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:682.2,682.12 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:642.33,644.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:646.33,647.14 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:648.25,650.18 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:654.4,654.43 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:655.27,656.76 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:657.26,658.52 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:659.36,660.42 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:661.29,662.41 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:663.29,664.41 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:665.11,666.37 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:650.18,652.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:670.79,672.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:674.101,676.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:678.68,680.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:685.99,686.49 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:686.49,689.17 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:692.3,692.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:703.3,705.44 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:689.17,691.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:692.34,694.42 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:694.42,700.5 4 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:709.94,712.40 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:722.2,722.23 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:712.40,713.41 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:713.41,714.36 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:714.36,716.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:716.10,716.47 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:716.47,718.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:725.100,726.44 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:729.2,730.50 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:734.2,734.46 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:941.2,941.48 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:946.2,946.46 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:951.2,951.46 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:956.2,956.46 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:960.2,960.24 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:726.44,728.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:730.50,732.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:734.46,736.66 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:742.3,743.37 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:746.3,746.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:749.3,749.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:752.3,752.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:755.3,755.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:758.3,758.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:761.3,761.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:764.3,764.81 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:767.3,767.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:770.3,770.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:773.3,773.45 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:777.3,777.51 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:780.3,780.37 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:784.3,784.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:791.3,791.28 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:795.3,795.34 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:799.3,799.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:802.3,802.35 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:805.3,805.36 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:811.3,811.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:814.3,814.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:818.3,818.38 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:822.3,822.35 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:825.3,825.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:828.3,828.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:831.3,831.26 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:835.3,835.24 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:838.3,838.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:847.3,847.30 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:850.3,850.29 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:853.3,853.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:856.3,856.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:859.3,859.33 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:862.3,862.24 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:865.3,865.30 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:869.3,869.53 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:872.3,872.49 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:875.3,875.28 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:878.3,878.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:881.3,881.36 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:884.3,884.51 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:887.3,887.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:890.3,890.30 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:894.3,894.39 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:897.3,897.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:900.3,900.25 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:903.3,903.32 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:934.3,934.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:937.3,937.45 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:736.66,738.12 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:739.9,741.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:743.37,745.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:746.31,748.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:749.32,751.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:752.33,754.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:755.32,757.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:758.32,760.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:761.34,763.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:764.81,766.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:767.34,769.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:770.32,772.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:773.45,775.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:777.51,779.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:780.37,782.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:784.32,789.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:791.28,794.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:795.34,798.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:799.31,801.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:802.35,804.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:805.36,807.42 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:807.42,809.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:811.32,813.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:814.31,817.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:818.38,821.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:822.35,824.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:825.29,827.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:828.33,830.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:831.26,833.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:835.24,837.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:838.31,840.35 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:843.4,843.37 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:840.35,842.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:843.37,845.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:847.30,849.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:850.29,852.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:853.31,855.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:856.32,858.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:859.33,861.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:862.24,864.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:865.30,868.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:869.53,871.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:872.49,874.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:875.28,877.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:878.32,880.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:881.36,883.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:884.51,886.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:887.31,889.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:890.30,893.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:894.39,896.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:897.32,899.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:900.25,902.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:903.32,913.52 3 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:918.4,918.46 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:926.4,928.29 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:932.4,932.35 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:913.52,916.5 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:918.46,919.57 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:922.5,922.51 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:919.57,921.6 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:928.29,930.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:934.31,936.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:941.48,943.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:946.46,948.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:951.46,953.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:956.46,958.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:963.69,964.26 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:968.2,970.49 2 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:985.2,985.47 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:991.2,991.18 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:964.26,966.3 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:970.49,971.45 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:980.3,980.81 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:971.45,972.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:975.4,975.27 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:972.27,974.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:975.27,977.5 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:980.81,982.4 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:985.47,986.31 1 0 github.com/kubernetes/kompose/pkg/loader/compose/v3.go:986.31,988.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:42.28,45.2 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:48.50,49.19 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:53.2,53.44 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:58.2,59.16 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:62.2,62.15 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:49.19,51.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:53.44,54.55 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:54.55,56.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:59.16,61.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:66.81,67.32 1 26 github.com/kubernetes/kompose/pkg/transformer/utils.go:70.2,70.28 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:67.32,69.3 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:73.81,78.29 3 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:83.2,83.31 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:89.2,89.29 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:95.2,100.60 2 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:110.2,112.29 3 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:115.2,115.86 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:119.2,119.8 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:78.29,80.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:83.31,86.3 2 6 github.com/kubernetes/kompose/pkg/transformer/utils.go:89.29,92.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:100.60,104.3 3 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:104.8,104.69 1 12 github.com/kubernetes/kompose/pkg/transformer/utils.go:104.69,107.3 2 6 github.com/kubernetes/kompose/pkg/transformer/utils.go:112.29,114.3 1 5 github.com/kubernetes/kompose/pkg/transformer/utils.go:115.86,118.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:131.88,138.41 2 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:162.2,162.22 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:180.2,180.32 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:186.2,186.27 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:195.2,195.8 1 13 github.com/kubernetes/kompose/pkg/transformer/utils.go:138.41,139.10 1 52 github.com/kubernetes/kompose/pkg/transformer/utils.go:140.37,141.24 1 17 github.com/kubernetes/kompose/pkg/transformer/utils.go:146.4,147.29 2 17 github.com/kubernetes/kompose/pkg/transformer/utils.go:151.4,152.35 2 17 github.com/kubernetes/kompose/pkg/transformer/utils.go:154.25,155.47 1 5 github.com/kubernetes/kompose/pkg/transformer/utils.go:156.11,157.37 1 30 github.com/kubernetes/kompose/pkg/transformer/utils.go:141.24,144.5 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:147.29,150.5 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:162.22,163.36 1 7 github.com/kubernetes/kompose/pkg/transformer/utils.go:163.36,165.4 1 3 github.com/kubernetes/kompose/pkg/transformer/utils.go:165.9,165.62 1 4 github.com/kubernetes/kompose/pkg/transformer/utils.go:165.62,167.4 1 4 github.com/kubernetes/kompose/pkg/transformer/utils.go:168.8,168.29 1 6 github.com/kubernetes/kompose/pkg/transformer/utils.go:168.29,171.3 2 3 github.com/kubernetes/kompose/pkg/transformer/utils.go:171.8,171.28 1 3 github.com/kubernetes/kompose/pkg/transformer/utils.go:171.28,174.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:180.32,183.3 2 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:186.27,188.3 1 4 github.com/kubernetes/kompose/pkg/transformer/utils.go:188.8,188.34 1 9 github.com/kubernetes/kompose/pkg/transformer/utils.go:188.34,191.3 2 9 github.com/kubernetes/kompose/pkg/transformer/utils.go:191.8,194.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:200.48,202.2 1 78 github.com/kubernetes/kompose/pkg/transformer/utils.go:206.52,207.32 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:211.2,211.16 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:207.32,210.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:214.36,216.2 1 66 github.com/kubernetes/kompose/pkg/transformer/utils.go:219.50,221.2 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:224.75,228.24 3 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:231.2,231.15 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:228.24,230.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:236.85,238.33 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:243.2,243.13 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:238.33,239.42 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:239.42,241.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:247.73,249.46 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:253.2,253.36 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:257.2,260.16 4 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:263.2,266.42 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:270.2,270.20 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:249.46,251.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:253.36,255.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:260.16,262.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:266.42,268.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:274.135,276.18 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:281.2,281.14 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:298.2,298.18 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:276.18,278.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:278.8,280.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:281.14,284.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:284.8,284.21 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:284.21,286.77 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:289.3,289.11 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:286.77,288.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:290.8,293.60 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:296.3,296.71 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:293.60,295.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:302.49,303.46 1 2 github.com/kubernetes/kompose/pkg/transformer/utils.go:308.2,308.17 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:303.46,305.3 1 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:305.8,305.54 1 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:305.54,307.3 1 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:315.30,317.2 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:321.40,323.2 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:326.35,328.2 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:331.61,334.40 2 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:341.2,342.37 2 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:334.40,336.17 2 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:339.3,339.48 1 1 github.com/kubernetes/kompose/pkg/transformer/utils.go:336.17,338.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:346.73,348.16 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:352.2,357.32 4 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:360.2,362.46 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:366.2,367.25 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:371.2,372.51 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:383.2,384.16 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:390.2,393.16 3 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:397.2,397.12 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:348.16,350.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:357.32,359.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:362.46,364.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:367.25,369.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:372.51,374.22 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:379.3,379.81 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:374.22,376.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:376.9,378.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:384.16,386.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:393.16,395.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:401.114,402.20 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:410.2,414.25 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:419.2,420.16 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:424.2,425.16 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:429.2,429.33 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:439.2,441.16 3 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:445.2,445.12 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:402.20,404.34 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:407.3,407.13 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:404.34,406.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:414.25,417.3 2 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:420.16,422.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:425.16,427.3 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:429.33,434.17 4 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:434.17,436.4 1 0 github.com/kubernetes/kompose/pkg/transformer/utils.go:441.16,443.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:53.41,63.16 5 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:67.2,67.16 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:80.2,82.16 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:87.2,98.16 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:101.2,105.16 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:109.2,110.12 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:63.16,65.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:67.16,69.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:73.3,74.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:69.17,71.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:74.17,76.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:82.16,84.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:98.16,100.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:105.16,107.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:114.39,117.16 2 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:120.2,124.16 3 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:129.2,129.22 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:132.2,132.19 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:117.16,119.3 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:124.16,126.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:129.22,131.3 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:135.52,137.19 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:147.2,147.16 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:137.19,139.22 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:139.22,143.4 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:143.9,145.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:150.62,159.2 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:162.76,168.77 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:175.2,176.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:179.2,179.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:182.2,182.15 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:193.2,197.30 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:276.2,276.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:282.2,282.12 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:168.77,169.63 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:169.63,171.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:176.16,178.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:179.21,181.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:182.15,184.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:187.3,187.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:190.3,190.18 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:184.17,186.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:187.28,189.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:197.30,200.34 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:209.3,212.17 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:215.3,216.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:219.3,220.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:223.3,223.34 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:200.34,202.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:206.4,206.65 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:202.18,204.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:212.17,214.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:216.17,218.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:220.17,222.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:224.8,226.22 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:230.3,230.57 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:234.3,236.29 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:226.22,228.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:230.57,232.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:236.29,238.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:241.4,242.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:246.4,249.52 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:268.4,269.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:273.4,273.31 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:238.18,240.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:242.18,244.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:249.52,257.5 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:257.10,266.5 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:269.18,271.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:276.21,278.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:278.17,280.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:286.88,288.16 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:293.2,293.16 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:296.2,296.8 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:288.16,290.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:290.8,292.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:293.16,295.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:300.55,309.16 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:313.2,316.48 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:319.2,319.23 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:309.16,311.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:316.48,318.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:325.67,327.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:331.2,332.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:336.2,336.15 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:327.16,329.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:332.16,334.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:341.67,343.51 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:347.2,347.17 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:343.51,345.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:365.69,367.2 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:369.118,383.2 8 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:385.97,388.21 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:392.2,392.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:396.2,396.13 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:388.21,391.3 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:392.21,395.3 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:400.93,407.39 4 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:415.2,418.12 3 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:407.39,410.3 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:410.8,412.3 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:426.101,444.2 8 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:445.158,450.60 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:457.2,457.50 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:462.2,462.31 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:476.2,476.12 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:450.60,454.3 3 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:457.50,459.3 1 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:462.31,464.17 2 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:467.3,467.31 1 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:464.17,466.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:467.31,468.33 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:469.28,470.71 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:471.37,472.74 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:480.151,483.16 2 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:488.2,489.16 2 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:493.2,493.28 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:499.2,499.61 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:508.2,508.16 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:515.2,524.60 4 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:627.2,627.50 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:632.2,632.31 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:658.2,658.12 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:483.16,485.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:489.16,491.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:493.28,497.3 3 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:499.61,503.25 1 11 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:503.25,505.4 1 11 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:508.16,509.25 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:509.25,511.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:524.60,533.78 9 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:536.3,542.36 5 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:549.3,555.24 3 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:564.3,564.30 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:569.3,570.25 2 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:573.3,573.25 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:583.3,583.62 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:588.3,588.50 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:591.3,591.72 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:594.3,598.83 3 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:605.3,605.74 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:612.3,612.29 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:615.3,615.31 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:619.3,619.84 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:623.3,623.13 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:533.78,535.4 1 23 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:542.36,544.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:544.18,546.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:555.24,556.29 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:556.30,558.5 0 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:558.10,560.5 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:564.30,566.4 1 11 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:570.25,572.4 1 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:573.25,575.18 2 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:575.18,577.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:577.10,579.5 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:583.62,585.4 1 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:588.50,590.4 1 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:591.72,593.4 1 11 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:598.83,600.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:600.9,602.4 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:605.74,607.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:607.9,609.4 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:612.29,614.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:615.31,617.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:619.84,621.4 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:627.50,629.3 1 23 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:632.31,634.17 2 73 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:637.3,637.31 1 73 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:634.17,636.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:637.31,638.33 1 49 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:639.28,640.71 1 7 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:641.37,642.74 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:643.29,645.35 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:648.5,649.47 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:654.5,654.63 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:645.35,646.11 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:649.47,653.6 3 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:662.64,664.36 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:667.2,667.11 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:664.36,666.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:672.75,673.21 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:676.2,676.22 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:679.2,679.11 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:673.21,675.3 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:676.22,678.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:693.151,696.58 2 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:706.2,706.27 1 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:696.58,698.20 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:698.20,703.4 4 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:710.90,712.52 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:727.2,727.64 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:741.2,741.8 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:712.52,715.28 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:719.3,719.28 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:723.3,723.63 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:715.28,717.4 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:719.28,721.4 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:727.64,730.34 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:734.3,734.34 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:738.3,738.68 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:730.34,732.4 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:734.34,736.4 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:745.70,746.16 1 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:757.2,757.16 1 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:747.10,747.10 0 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:748.16,749.29 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:750.15,751.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:752.22,753.35 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:754.10,755.88 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:761.72,762.17 1 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:763.27,764.38 1 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:765.20,766.37 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:767.20,768.41 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:769.10,770.86 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:777.64,780.28 2 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:787.2,789.13 3 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:780.28,781.63 1 119 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:781.63,783.4 1 23 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:783.9,785.4 1 96 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:796.63,799.28 3 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:813.2,813.16 1 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:799.28,800.40 1 119 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:800.40,802.16 2 119 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:802.16,804.13 2 5 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:805.10,808.5 2 114 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:809.9,811.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:817.63,819.49 2 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:822.2,823.19 2 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:819.49,821.3 1 34 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:827.56,828.13 1 5 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:831.2,832.16 2 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:835.2,836.16 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:828.13,830.3 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:832.16,834.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:840.90,843.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:846.2,850.16 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:854.2,854.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:843.16,845.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:850.16,852.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:858.54,860.16 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:863.2,863.31 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:860.16,862.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:867.40,872.2 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:875.41,883.2 2 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:886.46,889.2 2 33 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:891.61,893.36 2 40 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:896.2,896.13 1 40 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:893.36,895.3 1 33 github.com/kubernetes/kompose/pkg/transformer/kubernetes/k8sutils.go:900.45,902.2 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:75.121,79.61 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:104.2,104.18 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:79.61,84.32 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:84.32,86.69 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:86.69,87.38 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:87.38,90.75 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:97.6,99.37 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:90.75,91.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:91.27,93.16 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:108.92,109.17 1 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:112.2,120.22 2 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:127.2,127.12 1 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:109.17,111.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:120.22,126.3 1 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:131.117,135.40 3 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:176.2,187.35 2 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:194.2,194.12 1 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:135.40,138.19 3 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:142.3,147.17 5 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:152.3,157.24 2 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:162.3,173.35 3 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:138.19,141.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:147.17,150.12 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:157.24,160.4 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:187.35,193.3 1 8 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:200.87,216.2 2 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:219.114,221.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:227.2,242.18 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:221.16,223.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:248.135,262.16 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:266.2,266.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:294.2,294.23 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:262.16,264.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:267.20,269.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:273.3,273.30 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:283.3,283.40 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:285.24,291.4 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:269.17,271.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:273.30,274.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:274.21,277.19 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:280.5,280.32 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:277.19,279.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:299.46,300.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:303.2,303.45 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:306.2,306.13 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:300.27,302.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:303.45,305.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:309.74,313.25 3 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:322.2,323.32 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:313.25,315.13 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:315.13,317.4 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:317.9,319.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:327.120,329.16 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:333.2,334.54 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:339.2,352.18 4 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:329.16,331.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:334.54,335.33 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:335.33,337.4 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:356.105,358.30 2 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:364.2,392.19 5 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:408.2,408.11 1 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:358.30,360.3 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:360.8,362.3 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:392.19,398.29 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:401.3,402.35 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:405.3,405.99 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:398.29,400.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:402.35,404.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:412.91,429.2 2 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:431.107,433.30 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:438.2,459.11 3 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:433.30,435.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:435.8,437.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:462.112,481.29 5 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:511.2,511.36 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:528.2,528.16 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:481.29,483.14 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:486.3,506.21 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:483.14,485.4 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:506.21,509.4 2 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:511.36,512.41 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:512.41,519.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:519.9,525.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:532.96,534.50 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:559.2,559.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:534.50,535.24 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:535.24,537.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:541.4,554.37 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:537.18,540.5 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:555.9,557.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:563.154,565.16 2 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:569.2,587.28 2 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:593.2,593.18 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:599.2,599.31 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:603.2,603.17 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:565.16,567.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:587.28,591.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:593.18,595.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:595.8,597.3 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:599.31,601.3 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:607.69,610.36 3 38 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:626.2,626.14 1 38 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:610.36,611.23 1 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:614.3,619.75 2 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:622.3,623.26 2 31 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:611.23,612.12 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:619.75,621.4 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:629.113,632.36 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:654.2,654.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:632.36,633.25 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:636.3,646.75 5 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:633.25,635.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:646.75,649.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:649.9,652.4 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:658.90,663.36 4 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:700.2,700.21 1 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:663.36,664.25 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:668.3,674.49 5 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:682.3,688.90 2 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:693.3,693.75 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:697.3,698.45 2 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:664.25,666.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:674.49,676.66 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:679.4,679.69 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:676.66,678.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:688.90,690.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:693.75,695.4 1 25 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:704.74,707.40 3 34 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:710.2,710.42 1 34 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:713.2,716.3 1 34 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:707.40,709.3 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:710.42,712.3 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:720.112,725.43 3 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:746.2,746.30 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:725.43,745.3 7 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:755.120,758.30 3 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:826.2,826.30 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:758.30,759.48 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:759.48,760.30 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:763.4,763.30 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:767.4,770.33 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:798.4,808.32 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:813.4,823.49 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:760.30,762.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:763.30,765.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:770.33,775.5 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:775.10,781.40 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:790.5,790.61 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:795.5,795.24 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:781.40,784.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:784.11,787.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:790.61,793.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:808.32,811.5 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:830.169,842.33 9 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:847.2,847.57 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:857.2,863.41 5 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:957.2,957.46 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:842.33,844.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:847.57,848.41 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:851.3,853.35 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:848.41,850.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:863.41,866.30 2 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:883.3,893.22 3 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:943.3,952.62 4 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:866.30,867.23 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:878.4,879.11 2 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:867.23,869.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:869.10,869.26 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:869.26,871.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:871.10,871.27 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:871.27,873.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:873.10,875.5 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:880.9,882.4 1 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:893.22,895.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:895.9,895.25 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:895.25,897.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:900.4,900.22 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:897.18,899.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:901.9,901.26 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:901.26,904.99 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:904.99,906.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:906.10,910.28 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:910.28,912.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:914.9,916.26 2 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:916.26,919.35 3 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:922.5,922.32 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:934.5,936.19 2 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:940.5,940.36 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:919.35,921.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:922.32,924.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:924.11,925.45 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:925.45,926.39 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:926.39,928.8 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:928.13,928.60 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:928.60,930.8 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:936.19,938.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:952.62,954.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:962.76,964.20 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:971.2,973.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:964.20,968.3 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:977.121,980.25 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:997.2,999.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:980.25,982.26 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:985.3,985.32 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:988.3,995.4 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:982.26,984.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:985.32,987.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1003.89,1005.16 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1008.2,1009.27 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1013.2,1015.8 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1005.16,1007.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1009.27,1011.3 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1019.90,1026.2 1 17 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1029.98,1037.30 3 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1067.2,1067.40 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1079.2,1080.18 2 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1037.30,1040.40 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1040.40,1045.18 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1050.4,1050.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1045.18,1047.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1050.27,1062.5 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1067.40,1068.31 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1068.31,1073.4 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1084.66,1090.68 4 33 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1103.2,1103.17 1 33 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1090.68,1102.3 1 1 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1107.100,1112.36 3 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1117.2,1117.50 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1130.2,1130.20 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1112.36,1115.3 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1117.50,1128.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1133.118,1136.25 3 66 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1139.2,1139.31 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1147.2,1147.11 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1136.25,1138.3 1 64 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1139.31,1146.3 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1151.145,1155.51 3 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1162.2,1162.36 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1173.2,1173.64 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1183.2,1183.30 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1187.2,1187.59 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1191.2,1191.59 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1195.2,1195.47 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1199.2,1199.30 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1206.2,1206.16 1 30 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1155.51,1157.3 1 21 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1157.8,1159.3 1 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1162.36,1164.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1164.27,1167.4 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1167.9,1167.43 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1167.43,1169.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1173.64,1177.27 4 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1180.3,1180.23 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1177.27,1179.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1183.30,1185.3 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1187.59,1189.3 1 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1191.59,1193.3 1 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1195.47,1197.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1199.30,1200.43 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1200.43,1203.4 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1209.142,1210.41 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1220.2,1220.16 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1210.41,1213.41 3 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1216.3,1218.39 3 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1213.41,1214.12 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1224.83,1238.2 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1241.99,1267.2 3 36 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1269.102,1275.74 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1299.2,1299.12 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1275.74,1277.26 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1281.3,1281.26 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1285.3,1289.17 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1294.3,1295.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1277.26,1279.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1281.26,1283.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1289.17,1291.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1295.17,1297.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1302.131,1303.27 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1303.27,1304.44 1 19 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1304.44,1306.29 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1309.4,1309.21 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1306.29,1308.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1309.21,1311.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1312.9,1315.35 3 19 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1315.35,1317.5 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1319.8,1320.40 1 13 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1320.40,1323.4 2 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1323.9,1325.4 1 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1329.129,1330.30 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1341.2,1341.12 1 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1330.30,1331.39 1 18 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1331.39,1335.18 3 36 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1338.4,1338.35 1 36 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1335.18,1337.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1346.123,1350.34 2 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1359.2,1359.32 1 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1466.2,1467.34 2 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1509.2,1513.24 3 29 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1350.34,1352.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1355.3,1355.32 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1352.17,1354.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1355.32,1357.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1359.32,1362.69 3 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1362.69,1367.40 3 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1381.4,1383.34 2 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1463.4,1463.47 1 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1367.40,1368.35 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1368.35,1370.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1370.11,1372.32 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1375.6,1375.37 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1372.32,1374.7 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1383.34,1386.32 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1394.5,1398.73 4 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1402.5,1407.19 4 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1411.5,1411.31 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1416.5,1421.19 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1430.5,1430.19 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1436.5,1449.86 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1453.5,1454.19 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1458.5,1458.91 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1386.32,1388.24 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1391.6,1391.27 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1388.24,1390.7 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1398.73,1400.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1407.19,1409.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1411.31,1415.6 3 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1421.19,1425.28 1 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1425.28,1427.7 1 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1430.19,1431.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1431.28,1433.7 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1449.86,1451.6 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1454.19,1456.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1458.91,1460.6 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1467.34,1471.22 2 32 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1475.3,1479.63 3 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1484.3,1484.93 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1492.3,1492.48 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1495.3,1498.17 3 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1502.3,1502.82 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1505.3,1505.46 1 26 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1471.22,1472.12 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1479.63,1481.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1484.93,1488.4 3 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1488.9,1490.4 1 24 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1492.48,1494.4 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1498.17,1500.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1502.82,1504.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1517.162,1518.25 1 88 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1557.2,1557.12 1 88 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1519.26,1521.17 2 27 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1524.3,1524.28 1 27 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1525.25,1527.17 2 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1530.3,1530.28 1 3 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1531.27,1533.17 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1536.3,1536.28 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1537.35,1539.17 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1542.3,1542.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1543.16,1549.17 3 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1552.3,1553.30 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1554.29,1555.28 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1521.17,1523.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1527.17,1529.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1533.17,1535.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1539.17,1541.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/kubernetes.go:1549.17,1551.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:22.92,23.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:23.32,27.18 3 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:31.3,32.17 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:36.3,49.45 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:27.18,29.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:32.17,33.41 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:53.94,54.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:54.32,56.36 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:56.36,58.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:58.18,60.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:66.67,67.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:67.32,68.53 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:68.53,71.29 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:75.4,75.29 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:79.4,79.38 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:71.29,73.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:75.29,77.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:79.38,81.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:87.69,88.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:88.32,89.65 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:89.65,92.35 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:96.4,96.35 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:100.4,100.38 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:92.35,94.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:96.35,98.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:100.38,102.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:108.80,109.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:109.32,114.24 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:123.3,123.30 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:128.3,129.25 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:132.3,132.25 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:142.3,145.62 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:150.3,150.50 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:153.3,153.72 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:114.24,115.29 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:115.30,117.5 0 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:117.10,119.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:123.30,125.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:129.25,131.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:132.25,134.18 2 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:134.18,136.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:136.10,138.5 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:145.62,147.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:150.50,152.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:153.72,155.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:159.54,161.33 2 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:164.2,164.12 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:161.33,163.3 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:167.53,168.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:168.32,171.81 3 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:171.81,172.35 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:172.35,173.38 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:173.38,175.11 2 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:182.69,184.43 2 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:188.2,188.12 1 15 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:184.43,186.3 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:191.68,192.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:192.32,194.37 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:194.37,196.97 2 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:196.97,197.46 1 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:197.46,198.54 1 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:198.54,200.12 2 4 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:209.60,210.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:210.32,214.37 2 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:214.37,215.63 1 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:215.63,217.5 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:223.80,224.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:224.32,225.83 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:225.83,226.14 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:227.9,228.38 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:228.38,230.5 1 9 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:236.78,237.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:237.32,238.74 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:238.74,239.14 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:240.9,242.4 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:246.60,247.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:247.32,249.29 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:249.29,251.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:255.62,256.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:256.32,257.31 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:257.31,259.4 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:263.62,266.82 2 66 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:270.2,270.31 1 14 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:293.2,300.15 5 14 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:266.82,268.3 1 52 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:270.31,276.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:276.8,276.111 1 12 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:276.111,283.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:283.8,283.59 1 10 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:283.59,289.3 1 10 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:289.8,290.59 1 0 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:303.66,304.32 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:304.32,306.3 1 2 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:309.77,310.32 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:310.32,312.3 1 6 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:315.63,316.29 1 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:319.2,319.16 1 20 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:316.29,318.3 1 80 github.com/kubernetes/kompose/pkg/transformer/kubernetes/podspec.go:322.43,324.2 1 9 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:55.131,56.25 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:62.2,63.28 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:67.2,69.56 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:80.2,93.11 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:56.25,58.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:63.28,65.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:69.56,78.3 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:96.125,99.51 3 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:108.2,109.16 2 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:113.2,151.16 2 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:99.51,100.26 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:103.3,103.76 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:100.26,102.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:109.16,111.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:155.128,162.33 3 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:166.2,167.30 2 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:173.2,214.19 3 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:222.2,222.11 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:162.33,164.3 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:167.30,169.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:169.8,171.3 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:214.19,220.3 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:225.103,248.37 2 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:251.2,251.14 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:248.37,250.3 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:256.122,258.36 2 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:262.2,268.34 6 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:278.2,279.34 2 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:417.2,421.24 3 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:258.36,260.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:268.34,270.17 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:273.3,273.32 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:270.17,272.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:273.32,275.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:279.34,285.52 4 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:292.3,292.37 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:300.3,300.75 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:324.3,324.65 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:386.3,386.28 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:408.3,409.17 2 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:413.3,413.46 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:285.52,287.4 1 3 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:287.9,289.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:292.37,294.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:300.75,302.27 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:306.4,306.27 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:311.4,312.18 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:317.4,318.18 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:302.27,304.5 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:306.27,308.5 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:312.18,314.5 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:318.18,320.5 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:324.65,326.34 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:329.4,330.34 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:326.34,328.5 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:331.9,334.34 2 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:342.4,342.58 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:334.34,338.5 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:342.58,345.19 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:351.5,351.66 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:356.5,356.26 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:364.5,364.28 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:375.5,376.19 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:379.5,382.77 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:345.19,347.14 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:351.66,353.6 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:356.26,358.20 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:358.20,360.7 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:364.28,365.20 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:368.6,369.20 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:365.20,367.7 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:369.20,371.7 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:376.19,378.6 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:386.28,387.45 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:387.45,389.30 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:392.5,392.22 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:389.30,391.6 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:392.22,394.6 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:395.10,399.36 3 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:399.36,401.6 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:403.9,403.47 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:403.47,406.4 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/openshift.go:409.17,411.4 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:10.39,22.17 3 7 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:26.2,27.17 2 7 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:30.2,30.17 1 5 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:22.17,24.3 1 0 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:27.17,29.3 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:34.57,38.16 4 6 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:42.2,43.24 2 4 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:38.16,40.3 1 2 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:47.26,50.2 2 0 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:53.68,57.16 4 2 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:60.2,61.37 2 1 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:64.2,64.17 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:57.16,59.3 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:61.37,63.3 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:68.65,72.16 4 2 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:75.2,75.50 1 1 github.com/kubernetes/kompose/pkg/transformer/openshift/utils.go:72.16,74.3 1 1 github.com/kubernetes/kompose/pkg/utils/docker/build.go:42.114,47.16 3 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:50.2,54.16 3 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:59.2,60.16 2 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:65.2,78.16 5 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:82.2,84.12 2 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:47.16,49.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:54.16,56.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:60.16,62.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/build.go:78.16,80.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:26.39,34.25 3 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:43.2,43.16 1 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:47.2,47.20 1 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:34.25,38.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:38.8,42.3 2 0 github.com/kubernetes/kompose/pkg/utils/docker/client.go:43.16,45.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/image.go:36.63,45.2 1 4 github.com/kubernetes/kompose/pkg/utils/docker/image.go:50.77,57.16 3 4 github.com/kubernetes/kompose/pkg/utils/docker/image.go:62.2,62.26 1 4 github.com/kubernetes/kompose/pkg/utils/docker/image.go:71.2,73.19 2 4 github.com/kubernetes/kompose/pkg/utils/docker/image.go:57.16,59.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/image.go:62.26,66.17 3 2 github.com/kubernetes/kompose/pkg/utils/docker/image.go:66.17,68.4 1 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:37.45,53.16 5 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:60.2,61.9 2 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:70.2,72.16 3 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:77.2,79.12 3 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:53.16,55.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:55.8,57.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:61.9,68.3 2 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:72.16,75.3 2 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:88.63,92.64 3 0 github.com/kubernetes/kompose/pkg/utils/docker/push.go:92.64,94.3 1 0 github.com/kubernetes/kompose/pkg/utils/docker/tag.go:30.43,38.16 4 0 github.com/kubernetes/kompose/pkg/utils/docker/tag.go:43.2,44.12 2 0 github.com/kubernetes/kompose/pkg/utils/docker/tag.go:38.16,41.3 2 0 ================================================ FILE: index.md ================================================ --- layout: default --- # Kubernetes + Compose = Kompose ## A conversion tool to go from Compose to Kubernetes ### What's Kompose? Kompose is a conversion tool for Compose to container orchestrators such as Kubernetes (or OpenShift). Why do developers love it? - Simplify your development process with Compose and then deploy your containers to a production cluster - Convert your `docker-compose.yaml` with one simple command `kompose convert` ### It's as simple as 1-2-3 1. [Use an example docker-compose.yaml file](https://raw.githubusercontent.com/kubernetes/kompose/master/examples/docker-compose-v3.yaml) or your own 2. Run `kompose convert` 3. Run `kubectl apply` and check your Kubernetes cluster for your newly deployed containers! ```sh $ wget https://raw.githubusercontent.com/kubernetes/kompose/master/examples/docker-compose-v3.yaml -O docker-compose.yaml $ kompose convert $ kubectl apply -f . $ kubectl get po NAME READY STATUS RESTARTS AGE frontend-591253677-5t038 1/1 Running 0 10s redis-master-2410703502-9hshf 1/1 Running 0 10s redis-replica-4049176185-hr1lr 1/1 Running 0 10s ``` A more detailed guide is available in our [getting started guide](/docs/getting-started.md). ### Install Kompose on Linux, macOS or Windows Grab the Kompose binary! ```sh # Linux curl -L https://github.com/kubernetes/kompose/releases/download/v1.25.0/kompose-linux-amd64 -o kompose # macOS curl -L https://github.com/kubernetes/kompose/releases/download/v1.25.0/kompose-darwin-amd64 -o kompose chmod +x kompose sudo mv ./kompose /usr/local/bin/kompose ``` _Windows:_ Download from [GitHub](https://github.com/kubernetes/kompose/releases/download/v1.25.0/kompose-windows-amd64.exe) and add the binary to your PATH. ================================================ FILE: main.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package main import ( "fmt" "os" "github.com/kubernetes/kompose/cmd" ) func main() { if err := cmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } ================================================ FILE: pkg/app/app.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package app import ( "fmt" "path/filepath" "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime" "os" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader" "github.com/kubernetes/kompose/pkg/transformer" "github.com/kubernetes/kompose/pkg/transformer/kubernetes" "github.com/kubernetes/kompose/pkg/transformer/openshift" ) var ( // DefaultComposeFiles is a list of filenames that kompose will use if no file is explicitly set DefaultComposeFiles = []string{ "compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml", } ) const ( // ProviderKubernetes is provider kubernetes ProviderKubernetes = "kubernetes" // ProviderOpenshift is provider openshift ProviderOpenshift = "openshift" // DefaultProvider - provider that will be used if there is no provider was explicitly set DefaultProvider = ProviderKubernetes ) var inputFormat = "compose" // ValidateFlags validates all command line flags func ValidateFlags(args []string, cmd *cobra.Command, opt *kobject.ConvertOptions) { if opt.OutFile == "-" { opt.ToStdout = true opt.OutFile = "" } // Get the provider provider := cmd.Flags().Lookup("provider").Value.String() log.Debugf("Checking validation of provider: %s", provider) // OpenShift specific flags deploymentConfig := cmd.Flags().Lookup("deployment-config").Changed buildRepo := cmd.Flags().Lookup("build-repo").Changed buildBranch := cmd.Flags().Lookup("build-branch").Changed // Kubernetes specific flags chart := cmd.Flags().Lookup("chart").Changed daemonSet := cmd.Flags().Lookup("daemon-set").Changed replicationController := cmd.Flags().Lookup("replication-controller").Changed deployment := cmd.Flags().Lookup("deployment").Changed // Get the controller controller := opt.Controller log.Debugf("Checking validation of controller: %s", controller) // Check validations against provider flags switch { case provider == ProviderOpenshift: if chart { log.Fatalf("--chart, -c is a Kubernetes only flag") } if daemonSet { log.Fatalf("--daemon-set is a Kubernetes only flag") } if replicationController { log.Fatalf("--replication-controller is a Kubernetes only flag") } if deployment { log.Fatalf("--deployment, -d is a Kubernetes only flag") } if controller == "daemonset" || controller == "replicationcontroller" || controller == "deployment" { log.Fatalf("--controller= daemonset, replicationcontroller or deployment is a Kubernetes only flag") } case provider == ProviderKubernetes: if deploymentConfig { log.Fatalf("--deployment-config is an OpenShift only flag") } if buildRepo { log.Fatalf("--build-repo is an Openshift only flag") } if buildBranch { log.Fatalf("--build-branch is an Openshift only flag") } if controller == "deploymentconfig" { log.Fatalf("--controller=deploymentConfig is an OpenShift only flag") } } // Standard checks regardless of provider if len(opt.OutFile) != 0 && opt.ToStdout { log.Fatalf("Error: --out and --stdout can't be set at the same time") } if opt.CreateChart && opt.ToStdout { log.Fatalf("Error: chart cannot be generated when --stdout is specified") } if opt.Replicas < 0 { log.Fatalf("Error: --replicas cannot be negative") } if len(args) != 0 { log.Fatal("Unknown Argument(s): ", strings.Join(args, ",")) } if opt.GenerateJSON && opt.GenerateYaml { log.Fatalf("YAML and JSON format cannot be provided at the same time") } if _, ok := kubernetes.ValidVolumeSet[opt.Volumes]; !ok { validVolumesTypes := make([]string, 0) for validVolumeType := range kubernetes.ValidVolumeSet { validVolumesTypes = append(validVolumesTypes, fmt.Sprintf("'%s'", validVolumeType)) } log.Fatal("Unknown Volume type: ", opt.Volumes, ", possible values are: ", strings.Join(validVolumesTypes, " ")) } } // ValidateComposeFile validates the compose file provided for conversion func ValidateComposeFile(opt *kobject.ConvertOptions) error { if len(opt.InputFiles) == 0 { // Go through a range of "default" file names to see if tany ofthem exist in the current directory for _, name := range DefaultComposeFiles { _, err := os.Stat(name) if err != nil { log.Debugf("'%s' not found: %v", name, err) } else { opt.InputFiles = []string{name} return nil } } // Return an error message that no compose or docker-compose yaml files were found return fmt.Errorf("No compose or docker-compose yaml file found in the current directory") } return nil } func validateControllers(opt *kobject.ConvertOptions) { singleOutput := len(opt.OutFile) != 0 || opt.OutFile == "-" || opt.ToStdout if opt.Provider == ProviderKubernetes { // create deployment by default if no controller has been set if !opt.CreateD && !opt.CreateDS && !opt.CreateRC && opt.Controller == "" { opt.CreateD = true } if singleOutput { count := 0 if opt.CreateD { count++ } if opt.CreateDS { count++ } if opt.CreateRC { count++ } if count > 1 { log.Fatalf("Error: only one kind of Kubernetes resource can be generated when --out or --stdout is specified") } } } else if opt.Provider == ProviderOpenshift { // create deploymentconfig by default if no controller has been set if !opt.CreateDeploymentConfig { opt.CreateDeploymentConfig = true } if singleOutput { count := 0 if opt.CreateDeploymentConfig { count++ } // Add more controllers here once they are available in OpenShift // if opt.foo {count++} if count > 1 { log.Fatalf("Error: only one kind of OpenShift resource can be generated when --out or --stdout is specified") } } } } // Convert transforms docker compose or dab file to k8s objects func Convert(opt kobject.ConvertOptions) ([]runtime.Object, error) { validateControllers(&opt) // loader parses input from file into komposeObject. l, err := loader.GetLoader(inputFormat) if err != nil { log.Fatal(err) } komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), } komposeObject, err = l.LoadFile(opt.InputFiles, opt.Profiles, opt.NoInterpolate) if err != nil { log.Fatal(err) } komposeObject.Namespace = opt.Namespace // Get the directory of the compose file workDir, err := transformer.GetComposeFileDir(opt.InputFiles) if err != nil { log.Fatalf("Unable to get compose file directory: %s", err) } // convert env_file from absolute to relative path for _, service := range komposeObject.ServiceConfigs { if len(service.EnvFile) <= 0 { continue } for i, envFile := range service.EnvFile { if !filepath.IsAbs(envFile) { continue } relPath, err := filepath.Rel(workDir, envFile) if err != nil { log.Fatal(err) } service.EnvFile[i] = filepath.ToSlash(relPath) } } // Get a transformer that maps komposeObject to provider's primitives t := getTransformer(opt) // Do the transformation objects, err := t.Transform(komposeObject, opt) if err != nil { log.Fatal(err) } // Print output err = kubernetes.PrintList(objects, opt) if err != nil { log.Fatal(err) } return objects, err } // Convenience method to return the appropriate Transformer based on // what provider we are using. func getTransformer(opt kobject.ConvertOptions) transformer.Transformer { var t transformer.Transformer if opt.Provider == DefaultProvider { // Create/Init new Kubernetes object with CLI opts t = &kubernetes.Kubernetes{Opt: opt} } else { // Create/Init new OpenShift object that is initialized with a newly // created Kubernetes object. Openshift inherits from Kubernetes t = &openshift.OpenShift{Kubernetes: kubernetes.Kubernetes{Opt: opt}} } return t } ================================================ FILE: pkg/kobject/kobject.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package kobject import ( "path/filepath" "strconv" "time" "github.com/compose-spec/compose-go/v2/types" deployapi "github.com/openshift/api/apps/v1" "github.com/pkg/errors" "github.com/spf13/cast" v1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/util/intstr" ) // KomposeObject holds the generic struct of Kompose transformation type KomposeObject struct { ServiceConfigs map[string]ServiceConfig // LoadedFrom is name of the loader that created KomposeObject // Transformer need to know origin format in order to tell user what tag is not supported in origin format // as they can have different names. For example environment variables are called environment in compose but Env in bundle. LoadedFrom string Secrets types.Secrets // Namespace is the namespace where all the generated objects would be assigned to Namespace string } // ConvertOptions holds all options that controls transformation process type ConvertOptions struct { ToStdout bool CreateD bool CreateRC bool CreateDS bool CreateDeploymentConfig bool BuildRepo string BuildBranch string Build string Profiles []string PushImage bool PushImageRegistry string CreateChart bool GenerateYaml bool GenerateJSON bool StoreManifest bool EmptyVols bool Volumes string PVCRequestSize string InsecureRepository bool Replicas int InputFiles []string OutFile string Provider string Namespace string Controller string IsDeploymentFlag bool IsDaemonSetFlag bool IsReplicationControllerFlag bool IsReplicaSetFlag bool IsDeploymentConfigFlag bool IsNamespaceFlag bool BuildCommand string PushCommand string Server string YAMLIndent int WithKomposeAnnotation bool MultipleContainerMode bool ServiceGroupMode string ServiceGroupName string SecretsAsFiles bool GenerateNetworkPolicies bool NoInterpolate bool } // IsPodController indicate if the user want to use a controller func (opt *ConvertOptions) IsPodController() bool { return opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag || opt.Controller != "" } // ServiceConfigGroup holds an array of a ServiceConfig objects. type ServiceConfigGroup []ServiceConfig // ServiceConfig holds the basic struct of a container // which should not introduce any kubernetes specific struct type ServiceConfig struct { Name string ContainerName string Image string `compose:"image"` Environment []EnvVar `compose:"environment"` EnvFile []string `compose:"env_file"` Port []Ports `compose:"ports"` Command []string `compose:"command"` WorkingDir string `compose:""` DomainName string `compose:"domainname"` HostName string `compose:"hostname"` ReadOnly bool `compose:"read_only"` Args []string `compose:"args"` VolList []string `compose:"volumes"` NetworkMode string `compose:"network_mode"` Network []string `compose:"network"` Labels map[string]string `compose:"labels"` Annotations map[string]string `compose:""` CPUSet string `compose:"cpuset"` CPUShares int64 `compose:"cpu_shares"` CPUQuota int64 `compose:"cpu_quota"` CPULimit int64 `compose:""` CPUReservation int64 `compose:""` CapAdd []string `compose:"cap_add"` CapDrop []string `compose:"cap_drop"` Expose []string `compose:"expose"` ImagePullPolicy string `compose:"kompose.image-pull-policy"` Pid string `compose:"pid"` Privileged bool `compose:"privileged"` Restart string `compose:"restart"` User string `compose:"user"` VolumesFrom []string `compose:"volumes_from"` ServiceType string `compose:"kompose.service.type"` ServiceExternalTrafficPolicy string `compose:"kompose.service.external-traffic-policy"` NodePortPort int32 `compose:"kompose.service.nodeport.port"` StopGracePeriod string `compose:"stop_grace_period"` Build string `compose:"build"` BuildArgs map[string]*string `compose:"build-args"` ExposeContainerToHost bool `compose:"kompose.controller.port.expose"` ExposeService string `compose:"kompose.service.expose"` ExposeServicePath string `compose:"kompose.service.expose.path"` BuildLabels map[string]string `compose:"build-labels"` BuildTarget string `compose:""` ExposeServiceTLS string `compose:"kompose.service.expose.tls-secret"` ExposeServiceIngressClassName string `compose:"kompose.service.expose.ingress-class-name"` ImagePullSecret string `compose:"kompose.image-pull-secret"` Stdin bool `compose:"stdin_open"` Tty bool `compose:"tty"` MemLimit types.UnitBytes `compose:"mem_limit"` MemReservation types.UnitBytes `compose:""` DeployMode string `compose:""` VolumeMountSubPath string `compose:"kompose.volume.subpath"` // DeployLabels mapping to kubernetes labels DeployLabels map[string]string `compose:""` DeployUpdateConfig types.UpdateConfig `compose:""` TmpFs []string `compose:"tmpfs"` Dockerfile string `compose:"dockerfile"` Replicas int `compose:"replicas"` GroupAdd []int64 `compose:"group_add"` FsGroup int64 `compose:"kompose.security-context.fsgroup"` CronJobSchedule string `compose:"kompose.cronjob.schedule"` CronJobConcurrencyPolicy batchv1.ConcurrencyPolicy `compose:"kompose.cronjob.concurrency_policy"` CronJobBackoffLimit *int32 `compose:"kompose.cronjob.backoff_limit"` Volumes []Volumes `compose:""` Secrets []types.ServiceSecretConfig HealthChecks HealthChecks `compose:""` Placement Placement `compose:""` //This is for long LONG SYNTAX link(https://docs.docker.com/compose/compose-file/#long-syntax) Configs []types.ServiceConfigObjConfig `compose:""` //This is for SHORT SYNTAX link(https://docs.docker.com/compose/compose-file/#configs) ConfigsMetaData types.Configs `compose:""` WithKomposeAnnotation bool `compose:""` InGroup bool } // HealthChecks used to distinguish between liveness and readiness type HealthChecks struct { Liveness HealthCheck Readiness HealthCheck } // HealthCheck the healthcheck configuration for a service // "StartPeriod" was added to v3.4 of the compose, see: // https://github.com/docker/cli/issues/116 type HealthCheck struct { Test []string Timeout int32 Interval int32 Retries int32 StartPeriod int32 Disable bool HTTPPath string HTTPPort int32 TCPPort int32 } // EnvVar holds the environment variable struct of a container type EnvVar struct { Name string Value string } // Ports holds the ports struct of a container type Ports struct { HostPort int32 ContainerPort int32 HostIP string Protocol string // Upper string } // ID returns an unique id for this port settings, to avoid conflict func (port *Ports) ID() string { return strconv.Itoa(int(port.ContainerPort)) + port.Protocol } // Volumes holds the volume struct of container type Volumes struct { SvcName string // Service name to which volume is linked MountPath string // Mountpath extracted from docker-compose file VFrom string // denotes service name from which volume is coming VolumeName string // name of volume if provided explicitly Host string // host machine address Container string // Mountpath Mode string // access mode for volume PVCName string // name of PVC PVCSize string // PVC size SelectorValue string // Value of the label selector } // Placement holds the placement struct of container type Placement struct { PositiveConstraints map[string]string NegativeConstraints map[string]string Preferences []string } // GetConfigMapKeyFromMeta ... // given a source name ,find the file and extract the filename which will be act as ConfigMap key // return "" if not found func (s *ServiceConfig) GetConfigMapKeyFromMeta(name string) (string, error) { if s.ConfigsMetaData == nil { return "", errors.Errorf("config %s not found", name) } if _, ok := s.ConfigsMetaData[name]; !ok { return "", errors.Errorf("config %s not found", name) } config := s.ConfigsMetaData[name] if config.External { return "", errors.Errorf("config %s is external", name) } if config.File != "" { return filepath.Base(config.File), nil } else if config.Content != "" { // loop through s.Configs to find the config with the same name for _, cfg := range s.Configs { if cfg.Source == name { if cfg.Target == "" { return filepath.Base(cfg.Source), nil } else { return filepath.Base(cfg.Target), nil } } } } else { return "", errors.Errorf("config %s is empty", name) } return "", errors.Errorf("config %s not found", name) } // GetKubernetesUpdateStrategy from compose update_config // 1. only apply to Deployment, but the check is not happened here // 2. only support `parallelism` and `order` // return nil if not support func (s *ServiceConfig) GetKubernetesUpdateStrategy() *v1.RollingUpdateDeployment { config := s.DeployUpdateConfig r := v1.RollingUpdateDeployment{} if config.Order == "stop-first" { if config.Parallelism != nil { v := intstr.FromInt(cast.ToInt(*config.Parallelism)) r.MaxUnavailable = &v } v := intstr.FromInt(0) r.MaxSurge = &v return &r } if config.Order == "start-first" { if config.Parallelism != nil { v := intstr.FromInt(cast.ToInt(*config.Parallelism)) r.MaxSurge = &v } v := intstr.FromInt(0) r.MaxUnavailable = &v return &r } return nil } // GetOSUpdateStrategy ... func (s *ServiceConfig) GetOSUpdateStrategy() *deployapi.RollingDeploymentStrategyParams { config := s.DeployUpdateConfig r := deployapi.RollingDeploymentStrategyParams{} delay := time.Second * 1 if config.Delay != 0 { delay = time.Duration(config.Delay) } interval := cast.ToInt64(delay.Seconds()) if config.Order == "stop-first" { if config.Parallelism != nil { v := intstr.FromInt(cast.ToInt(*config.Parallelism)) r.MaxUnavailable = &v } *r.MaxSurge = intstr.FromInt(0) r.UpdatePeriodSeconds = &interval return &r } if config.Order == "start-first" { if config.Parallelism != nil { v := intstr.FromInt(cast.ToInt(*config.Parallelism)) r.MaxSurge = &v } v := intstr.FromInt(0) r.MaxUnavailable = &v r.UpdatePeriodSeconds = &interval return &r } if cast.ToInt64(config.Delay) != 0 { r.UpdatePeriodSeconds = &interval return &r } return nil } ================================================ FILE: pkg/loader/compose/compose.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package compose import ( "context" "fmt" "os" "reflect" "strconv" "strings" "time" "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/types" "github.com/fatih/structs" "github.com/google/shlex" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/transformer" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cast" batchv1 "k8s.io/api/batch/v1" api "k8s.io/api/core/v1" ) // StdinData is data bytes read from stdin var StdinData []byte // Compose is docker compose file loader, implements Loader interface type Compose struct { } // checkUnsupportedKey checks if compose-go project contains // keys that are not supported by this loader. // list of all unsupported keys are stored in unsupportedKey variable // returns list of unsupported YAML keys from docker-compose func checkUnsupportedKey(composeProject *types.Project) []string { // list of all unsupported keys for this loader // this is map to make searching for keys easier // to make sure that unsupported key is not going to be reported twice // by keeping record if already saw this key in another service var unsupportedKey = map[string]bool{ "CgroupParent": false, "CPUSet": false, "CPUShares": false, "Devices": false, "DependsOn": false, "DNS": false, "DNSSearch": false, "EnvFile": false, "ExternalLinks": false, "ExtraHosts": false, "Ipc": false, "Logging": false, "MacAddress": false, "MemSwapLimit": false, "NetworkMode": false, "SecurityOpt": false, "ShmSize": false, "StopSignal": false, "VolumeDriver": false, "Uts": false, "ReadOnly": false, "Ulimits": false, "Net": false, "Sysctls": false, //"Networks": false, // We shall be spporting network now. There are special checks for Network in checkUnsupportedKey function "Links": false, } var keysFound []string // Root level keys are not yet supported except Network // Check to see if the default network is available and length is only equal to one. if _, ok := composeProject.Networks["default"]; ok && len(composeProject.Networks) == 1 { log.Debug("Default network found") } // Root level volumes are not yet supported if len(composeProject.Volumes) > 0 { keysFound = append(keysFound, "root level volumes") } for _, serviceConfig := range composeProject.AllServices() { // this reflection is used in check for empty arrays val := reflect.ValueOf(serviceConfig) s := structs.New(serviceConfig) for _, f := range s.Fields() { // Check if given key is among unsupported keys, and skip it if we already saw this key if alreadySaw, ok := unsupportedKey[f.Name()]; ok && !alreadySaw { if f.IsExported() && !f.IsZero() { // IsZero returns false for empty array/slice ([]) // this check if field is Slice, and then it checks its size if field := val.FieldByName(f.Name()); field.Kind() == reflect.Slice { if field.Len() == 0 { // array is empty it doesn't matter if it is in unsupportedKey or not continue } } //get yaml tag name instead of variable name yamlTagName := strings.Split(f.Tag("yaml"), ",")[0] if f.Name() == "Networks" { // networks always contains one default element, even it isn't declared in compose v2. if len(serviceConfig.Networks) == 1 && serviceConfig.NetworksByPriority()[0] == "default" { // this is empty Network definition, skip it continue } } if linksArray := val.FieldByName(f.Name()); f.Name() == "Links" && linksArray.Kind() == reflect.Slice { //Links has "SERVICE:ALIAS" style, we don't support SERVICE != ALIAS findUnsupportedLinksFlag := false for i := 0; i < linksArray.Len(); i++ { if tmpLink := linksArray.Index(i); tmpLink.Kind() == reflect.String { tmpLinkStr := tmpLink.String() tmpLinkStrSplit := strings.Split(tmpLinkStr, ":") if len(tmpLinkStrSplit) == 2 && tmpLinkStrSplit[0] != tmpLinkStrSplit[1] { findUnsupportedLinksFlag = true break } } } if !findUnsupportedLinksFlag { continue } } keysFound = append(keysFound, yamlTagName) unsupportedKey[f.Name()] = true } } } } return keysFound } // LoadFile loads a compose file into KomposeObject func (c *Compose) LoadFile(files []string, profiles []string, noInterpolate bool) (kobject.KomposeObject, error) { // Gather the working directory workingDir, err := transformer.GetComposeFileDir(files) if err != nil { return kobject.KomposeObject{}, err } projectOptions, err := cli.NewProjectOptions( files, cli.WithOsEnv, cli.WithWorkingDirectory(workingDir), cli.WithInterpolation(!noInterpolate), cli.WithProfiles(profiles), cli.WithEnvFiles([]string{}...), cli.WithDotEnv, ) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "Unable to create compose options") } project, err := cli.ProjectFromOptions(context.Background(), projectOptions) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "Unable to load files") } // Finding 0 services means two things: // 1. The compose project is empty // 2. The profile that is configured in the compose project is different than the one defined in Kompose convert options // In both cases we should provide the user with a warning indicating that we didn't find any service. if len(project.Services) == 0 { log.Warning("No service selected. The profile specified in services of your compose yaml may not exist.") } komposeObject, err := dockerComposeToKomposeMapping(project) if err != nil { return kobject.KomposeObject{}, err } return komposeObject, nil } func loadPlacement(placement types.Placement) kobject.Placement { komposePlacement := kobject.Placement{ PositiveConstraints: make(map[string]string), NegativeConstraints: make(map[string]string), Preferences: make([]string, 0, len(placement.Preferences)), } // Convert constraints equal, notEqual := " == ", " != " for _, j := range placement.Constraints { operator := equal if strings.Contains(j, notEqual) { operator = notEqual } p := strings.Split(j, operator) if len(p) < 2 { log.Warnf("Failed to parse placement constraints %s, the correct format is 'label == xxx'", j) continue } key, err := convertDockerLabel(p[0]) if err != nil { log.Warn("Ignore placement constraints: ", err.Error()) continue } if operator == equal { komposePlacement.PositiveConstraints[key] = p[1] } else if operator == notEqual { komposePlacement.NegativeConstraints[key] = p[1] } } // Convert preferences for _, p := range placement.Preferences { // Spread is the only supported strategy currently label, err := convertDockerLabel(p.Spread) if err != nil { log.Warn("Ignore placement preferences: ", err.Error()) continue } komposePlacement.Preferences = append(komposePlacement.Preferences, label) } return komposePlacement } // Convert docker label to k8s label func convertDockerLabel(dockerLabel string) (string, error) { switch dockerLabel { case "node.hostname": return "kubernetes.io/hostname", nil case "engine.labels.operatingsystem": return "kubernetes.io/os", nil default: if strings.HasPrefix(dockerLabel, "node.labels.") { return strings.TrimPrefix(dockerLabel, "node.labels."), nil } } errMsg := fmt.Sprint(dockerLabel, " is not supported, only 'node.hostname', 'engine.labels.operatingsystem' and 'node.labels.xxx' (ex: node.labels.something == anything) is supported") return "", errors.New(errMsg) } // Convert the Compose volumes to []string (the old way) // TODO: Check to see if it's a "bind" or "volume". Ignore for now. // TODO: Refactor it similar to loadPorts // See: https://docs.docker.com/compose/compose-file/#long-syntax-3 func loadVolumes(volumes []types.ServiceVolumeConfig) []string { var volArray []string for _, vol := range volumes { // There will *always* be Source when parsing v := vol.Source if vol.Target != "" { v = v + ":" + vol.Target } if vol.ReadOnly { v = v + ":ro" } volArray = append(volArray, v) } return volArray } // Convert Compose ports to kobject.Ports // expose ports will be treated as TCP ports func loadPorts(ports []types.ServicePortConfig, expose []string) []kobject.Ports { komposePorts := []kobject.Ports{} exist := map[string]bool{} for _, port := range ports { // Convert to a kobject struct with ports komposePorts = append(komposePorts, kobject.Ports{ HostPort: cast.ToInt32(port.Published), ContainerPort: int32(port.Target), HostIP: port.HostIP, Protocol: strings.ToUpper(port.Protocol), }) exist[cast.ToString(port.Target)+port.Protocol] = true } for _, port := range expose { portValue := port protocol := string(api.ProtocolTCP) if strings.Contains(portValue, "/") { splits := strings.Split(port, "/") portValue = splits[0] protocol = splits[1] } if exist[portValue+protocol] { continue } komposePorts = append(komposePorts, kobject.Ports{ ContainerPort: cast.ToInt32(portValue), HostIP: "", Protocol: strings.ToUpper(protocol), }) } return komposePorts } /* Convert the HealthCheckConfig as designed by Docker to a Kubernetes-compatible format. */ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) { var test []string var httpPath string var httpPort, tcpPort, timeout, interval, retries, startPeriod int32 var disable bool for key, value := range labels { switch key { case HealthCheckReadinessDisable: disable = cast.ToBool(value) case HealthCheckReadinessTest: if len(value) > 0 { test, _ = shlex.Split(value) } case HealthCheckReadinessHTTPGetPath: httpPath = value case HealthCheckReadinessHTTPGetPort: httpPort = cast.ToInt32(value) case HealthCheckReadinessTCPPort: tcpPort = cast.ToInt32(value) case HealthCheckReadinessInterval: parse, err := time.ParseDuration(value) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check interval variable") } interval = int32(parse.Seconds()) case HealthCheckReadinessTimeout: parse, err := time.ParseDuration(value) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check timeout variable") } timeout = int32(parse.Seconds()) case HealthCheckReadinessRetries: retries = cast.ToInt32(value) case HealthCheckReadinessStartPeriod: parse, err := time.ParseDuration(value) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check startPeriod variable") } startPeriod = int32(parse.Seconds()) } } if len(test) > 0 { if test[0] == "NONE" { disable = true test = test[1:] } // Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test if test[0] == "CMD" || test[0] == "CMD-SHELL" { test = test[1:] } } return kobject.HealthCheck{ Test: test, HTTPPath: httpPath, HTTPPort: httpPort, TCPPort: tcpPort, Timeout: timeout, Interval: interval, Retries: retries, StartPeriod: startPeriod, Disable: disable, }, nil } /* Convert the HealthCheckConfig as designed by Docker to a Kubernetes-compatible format. */ func parseHealthCheck(composeHealthCheck types.HealthCheckConfig, labels types.Labels) (kobject.HealthCheck, error) { var httpPort, tcpPort, timeout, interval, retries, startPeriod int32 var test []string var httpPath string // Here we convert the timeout from 1h30s (example) to 36030 seconds. if composeHealthCheck.Timeout != nil { parse, err := time.ParseDuration(composeHealthCheck.Timeout.String()) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check timeout variable") } timeout = int32(parse.Seconds()) } if composeHealthCheck.Interval != nil { parse, err := time.ParseDuration(composeHealthCheck.Interval.String()) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check interval variable") } interval = int32(parse.Seconds()) } if composeHealthCheck.Retries != nil { retries = int32(*composeHealthCheck.Retries) } if composeHealthCheck.StartPeriod != nil { parse, err := time.ParseDuration(composeHealthCheck.StartPeriod.String()) if err != nil { return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check startPeriod variable") } startPeriod = int32(parse.Seconds()) } if composeHealthCheck.Test != nil { test = composeHealthCheck.Test[1:] } for key, value := range labels { switch key { case HealthCheckLivenessHTTPGetPath: httpPath = value case HealthCheckLivenessHTTPGetPort: httpPort = cast.ToInt32(value) case HealthCheckLivenessTCPPort: tcpPort = cast.ToInt32(value) } } // Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test return kobject.HealthCheck{ Test: test, TCPPort: tcpPort, HTTPPath: httpPath, HTTPPort: httpPort, Timeout: timeout, Interval: interval, Retries: retries, StartPeriod: startPeriod, }, nil } func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.KomposeObject, error) { // Step 1. Initialize what's going to be returned komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), LoadedFrom: "compose", Secrets: composeObject.Secrets, } // Step 2. Parse through the object and convert it to kobject.KomposeObject! // Here we "clean up" the service configuration so we return something that includes // all relevant information as well as avoid the unsupported keys as well. for _, composeServiceConfig := range composeObject.Services { // Standard import // No need to modify before importation name := parseResourceName(composeServiceConfig.Name, composeServiceConfig.Labels) serviceConfig := kobject.ServiceConfig{} serviceConfig.Name = name serviceConfig.Image = composeServiceConfig.Image serviceConfig.WorkingDir = composeServiceConfig.WorkingDir serviceConfig.Annotations = composeServiceConfig.Labels serviceConfig.CapAdd = composeServiceConfig.CapAdd serviceConfig.CapDrop = composeServiceConfig.CapDrop serviceConfig.Expose = composeServiceConfig.Expose serviceConfig.Privileged = composeServiceConfig.Privileged serviceConfig.User = composeServiceConfig.User serviceConfig.ReadOnly = composeServiceConfig.ReadOnly serviceConfig.Stdin = composeServiceConfig.StdinOpen serviceConfig.Tty = composeServiceConfig.Tty serviceConfig.TmpFs = composeServiceConfig.Tmpfs serviceConfig.ContainerName = normalizeContainerNames(composeServiceConfig.ContainerName) serviceConfig.Command = composeServiceConfig.Entrypoint serviceConfig.Args = composeServiceConfig.Command serviceConfig.Labels = composeServiceConfig.Labels serviceConfig.HostName = composeServiceConfig.Hostname serviceConfig.DomainName = composeServiceConfig.DomainName serviceConfig.Secrets = composeServiceConfig.Secrets serviceConfig.NetworkMode = composeServiceConfig.NetworkMode if composeServiceConfig.StopGracePeriod != nil { serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod.String() } if err := parseNetwork(&composeServiceConfig, &serviceConfig, composeObject); err != nil { return kobject.KomposeObject{}, err } if err := parseResources(&composeServiceConfig, &serviceConfig); err != nil { return kobject.KomposeObject{}, err } serviceConfig.Restart = composeServiceConfig.Restart if composeServiceConfig.Deploy != nil { // Deploy keys // mode: serviceConfig.DeployMode = composeServiceConfig.Deploy.Mode // labels serviceConfig.DeployLabels = composeServiceConfig.Deploy.Labels // restart-policy: deploy.restart_policy.condition will rewrite restart option // see: https://docs.docker.com/compose/compose-file/#restart_policy if composeServiceConfig.Deploy.RestartPolicy != nil { serviceConfig.Restart = composeServiceConfig.Deploy.RestartPolicy.Condition } // replicas: if composeServiceConfig.Deploy.Replicas != nil { serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas) } // placement: serviceConfig.Placement = loadPlacement(composeServiceConfig.Deploy.Placement) if composeServiceConfig.Deploy.UpdateConfig != nil { serviceConfig.DeployUpdateConfig = *composeServiceConfig.Deploy.UpdateConfig } if composeServiceConfig.Deploy.EndpointMode == "vip" { serviceConfig.ServiceType = string(api.ServiceTypeNodePort) } } // HealthCheck Liveness if composeServiceConfig.HealthCheck != nil && !composeServiceConfig.HealthCheck.Disable { var err error serviceConfig.HealthChecks.Liveness, err = parseHealthCheck(*composeServiceConfig.HealthCheck, composeServiceConfig.Labels) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "Unable to parse health check") } } // HealthCheck Readiness var readiness, errReadiness = parseHealthCheckReadiness(composeServiceConfig.Labels) if !readiness.Disable { serviceConfig.HealthChecks.Readiness = readiness if errReadiness != nil { return kobject.KomposeObject{}, errors.Wrap(errReadiness, "Unable to parse health check") } } if serviceConfig.Restart == "unless-stopped" { log.Warnf("Restart policy 'unless-stopped' in service %s is not supported, convert it to 'always'", name) serviceConfig.Restart = "always" } if composeServiceConfig.Build != nil { serviceConfig.Build = composeServiceConfig.Build.Context serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile serviceConfig.BuildArgs = composeServiceConfig.Build.Args serviceConfig.BuildLabels = composeServiceConfig.Build.Labels serviceConfig.BuildTarget = composeServiceConfig.Build.Target } // env parseEnvironment(&composeServiceConfig, &serviceConfig) // Get env_file parseEnvFiles(&composeServiceConfig, &serviceConfig) // Parse the ports // v3 uses a new format called "long syntax" starting in 3.2 // https://docs.docker.com/compose/compose-file/#ports // here we will translate `expose` too, they basically means the same thing in kubernetes serviceConfig.Port = loadPorts(composeServiceConfig.Ports, serviceConfig.Expose) // Parse the volumes // Again, in v3, we use the "long syntax" for volumes in terms of parsing // https://docs.docker.com/compose/compose-file/#long-syntax-3 serviceConfig.VolList = loadVolumes(composeServiceConfig.Volumes) if err := parseKomposeLabels(composeServiceConfig.Labels, &serviceConfig); err != nil { return kobject.KomposeObject{}, err } // Log if the name will been changed if normalizeServiceNames(name) != name { log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) } serviceConfig.Configs = composeServiceConfig.Configs serviceConfig.ConfigsMetaData = composeObject.Configs // Get GroupAdd, group should be mentioned in gid format but not the group name groupAdd, err := getGroupAdd(composeServiceConfig.GroupAdd) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "GroupAdd should be mentioned in gid format, not a group name") } serviceConfig.GroupAdd = groupAdd // Final step, add to the array! komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig } handleVolume(&komposeObject, &composeObject.Volumes) return komposeObject, nil } func parseNetwork(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig, composeObject *types.Project) error { if len(composeServiceConfig.Networks) == 0 { if defaultNetwork, ok := composeObject.Networks["default"]; ok { normalizedNetworkName, err := normalizeNetworkNames(defaultNetwork.Name) if err != nil { return errors.Wrap(err, "Unable to normalize network name") } serviceConfig.Network = append(serviceConfig.Network, normalizedNetworkName) } } else { var alias = "" for key := range composeServiceConfig.Networks { alias = key netName := composeObject.Networks[alias].Name // if Network Name Field is empty in the docker-compose definition // we will use the alias name defined in service config file if netName == "" { netName = alias } normalizedNetworkName, err := normalizeNetworkNames(netName) if err != nil { return errors.Wrap(err, "Unable to normalize network name") } serviceConfig.Network = append(serviceConfig.Network, normalizedNetworkName) } } return nil } func parseResources(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) error { serviceConfig.MemLimit = composeServiceConfig.MemLimit if composeServiceConfig.Deploy != nil { // memory: // See: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ // "The expression 0.1 is equivalent to the expression 100m, which can be read as “one hundred millicpu”." // Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing if composeServiceConfig.Deploy.Resources.Limits != nil { serviceConfig.MemLimit = composeServiceConfig.Deploy.Resources.Limits.MemoryBytes if composeServiceConfig.Deploy.Resources.Limits.NanoCPUs > 0 { serviceConfig.CPULimit = int64(composeServiceConfig.Deploy.Resources.Limits.NanoCPUs * 1000) } } if composeServiceConfig.Deploy.Resources.Reservations != nil { serviceConfig.MemReservation = composeServiceConfig.Deploy.Resources.Reservations.MemoryBytes if composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs > 0 { serviceConfig.CPUReservation = int64(composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs * 1000) } } } return nil } func parseEnvironment(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) { // Gather the environment values // DockerCompose uses map[string]*string while we use []string // So let's convert that using this hack // Note: unset env pick up the env value on host if exist for name, value := range composeServiceConfig.Environment { var env kobject.EnvVar if value != nil { env = kobject.EnvVar{Name: name, Value: *value} } else { result, ok := os.LookupEnv(name) if ok { env = kobject.EnvVar{Name: name, Value: result} } else { continue } } serviceConfig.Environment = append(serviceConfig.Environment, env) } } func parseEnvFiles(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) { for _, value := range composeServiceConfig.EnvFiles { serviceConfig.EnvFile = append(serviceConfig.EnvFile, value.Path) // value.Required is ignored } } func handleCronJobConcurrencyPolicy(policy string) (batchv1.ConcurrencyPolicy, error) { switch policy { case "Allow": return batchv1.AllowConcurrent, nil case "Forbid": return batchv1.ForbidConcurrent, nil case "Replace": return batchv1.ReplaceConcurrent, nil case "": return "", nil default: return "", fmt.Errorf("invalid cronjob concurrency policy: %s", policy) } } func handleCronJobBackoffLimit(backoffLimit string) (*int32, error) { if backoffLimit == "" { return nil, nil } limit, err := cast.ToInt32E(backoffLimit) if err != nil { return nil, fmt.Errorf("invalid cronjob backoff limit: %s", backoffLimit) } return &limit, nil } func handleCronJobSchedule(schedule string) (string, error) { if schedule == "" { return "", fmt.Errorf("cronjob schedule cannot be empty") } return schedule, nil } // parseKomposeLabels parse kompose labels, also do some validation func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.ServiceConfig) error { // Label handler // Labels used to influence conversion of kompose will be handled // from here for docker-compose. Each loader will have such handler. if serviceConfig.Labels == nil { serviceConfig.Labels = make(map[string]string) } for key, value := range labels { switch key { case LabelServiceType: serviceType, err := handleServiceType(value) if err != nil { return errors.Wrap(err, "handleServiceType failed") } serviceConfig.ServiceType = serviceType case LabelServiceExternalTrafficPolicy: serviceExternalTypeTrafficPolicy, err := handleServiceExternalTrafficPolicy(value) if err != nil { return errors.Wrap(err, "handleServiceExternalTrafficPolicy failed") } serviceConfig.ServiceExternalTrafficPolicy = serviceExternalTypeTrafficPolicy case LabelSecurityContextFsGroup: serviceConfig.FsGroup = cast.ToInt64(value) case LabelExposeContainerToHost: serviceConfig.ExposeContainerToHost = cast.ToBool(value) case LabelServiceExpose: serviceConfig.ExposeService = strings.Trim(value, " ,") case LabelNodePortPort: serviceConfig.NodePortPort = cast.ToInt32(value) case LabelServiceExposeTLSSecret: serviceConfig.ExposeServiceTLS = value case LabelServiceExposeIngressClassName: serviceConfig.ExposeServiceIngressClassName = value case LabelImagePullSecret: serviceConfig.ImagePullSecret = value case LabelImagePullPolicy: serviceConfig.ImagePullPolicy = value case LabelContainerVolumeSubpath: serviceConfig.VolumeMountSubPath = value case LabelCronJobSchedule: cronJobSchedule, err := handleCronJobSchedule(value) if err != nil { return errors.Wrap(err, "handleCronJobSchedule failed") } serviceConfig.CronJobSchedule = cronJobSchedule case LabelCronJobConcurrencyPolicy: cronJobConcurrencyPolicy, err := handleCronJobConcurrencyPolicy(value) if err != nil { return errors.Wrap(err, "handleCronJobConcurrencyPolicy failed") } serviceConfig.CronJobConcurrencyPolicy = cronJobConcurrencyPolicy case LabelCronJobBackoffLimit: cronJobBackoffLimit, err := handleCronJobBackoffLimit(value) if err != nil { return errors.Wrap(err, "handleCronJobBackoffLimit failed") } serviceConfig.CronJobBackoffLimit = cronJobBackoffLimit case LabelNameOverride: // generate a valid k8s resource name normalizedName := normalizeServiceNames(value) serviceConfig.Name = normalizedName default: serviceConfig.Labels[key] = value } } if serviceConfig.ExposeService == "" && serviceConfig.ExposeServiceTLS != "" { return errors.New("kompose.service.expose.tls-secret was specified without kompose.service.expose") } if serviceConfig.ExposeService == "" && serviceConfig.ExposeServiceIngressClassName != "" { return errors.New("kompose.service.expose.ingress-class-name was specified without kompose.service.expose") } if serviceConfig.ServiceType != string(api.ServiceTypeNodePort) && serviceConfig.NodePortPort != 0 { return errors.New("kompose.service.type must be nodeport when assign node port value") } if len(serviceConfig.Port) > 1 && serviceConfig.NodePortPort != 0 { return errors.New("cannot set kompose.service.nodeport.port when service has multiple ports") } if serviceConfig.Restart == "always" && serviceConfig.CronJobConcurrencyPolicy != "" { log.Infof("cronjob restart policy will be converted from '%s' to 'on-failure'", serviceConfig.Restart) serviceConfig.Restart = "on-failure" } return nil } func handleVolume(komposeObject *kobject.KomposeObject, volumes *types.Volumes) { for name := range komposeObject.ServiceConfigs { // retrieve volumes of service vols, err := retrieveVolume(name, *komposeObject) if err != nil { errors.Wrap(err, "could not retrieve vvolume") } for volName, vol := range vols { size, selector := getVolumeLabels(vol.VolumeName, volumes) if len(size) > 0 || len(selector) > 0 { // We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here var temp = vols[volName] temp.PVCSize = size temp.SelectorValue = selector vols[volName] = temp } } // We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here var temp = komposeObject.ServiceConfigs[name] temp.Volumes = vols komposeObject.ServiceConfigs[name] = temp } } // returns all volumes associated with service, if `volumes_from` key is used, we have to retrieve volumes from the services which are mentioned there. Hence, recursive function is used here. func retrieveVolume(svcName string, komposeObject kobject.KomposeObject) (volume []kobject.Volumes, err error) { // if volumes-from key is present if komposeObject.ServiceConfigs[svcName].VolumesFrom != nil { // iterating over services from `volumes-from` for _, depSvc := range komposeObject.ServiceConfigs[svcName].VolumesFrom { // recursive call for retrieving volumes of services from `volumes-from` dVols, err := retrieveVolume(depSvc, komposeObject) if err != nil { return nil, errors.Wrapf(err, "could not retrieve the volume") } var cVols []kobject.Volumes cVols, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName) if err != nil { return nil, errors.Wrapf(err, "error generating current volumes") } for _, cv := range cVols { // check whether volumes of current service is same or not as that of dependent volumes coming from `volumes-from` ok, dv := getVol(cv, dVols) if ok { // change current volumes service name to dependent service name if dv.VFrom == "" { cv.VFrom = dv.SvcName cv.SvcName = dv.SvcName } else { cv.VFrom = dv.VFrom cv.SvcName = dv.SvcName } cv.PVCName = dv.PVCName } volume = append(volume, cv) } // iterating over dependent volumes for _, dv := range dVols { // check whether dependent volume is already present or not if checkVolDependent(dv, volume) { // if found, add service name to `VFrom` dv.VFrom = dv.SvcName volume = append(volume, dv) } } } } else { // if `volumes-from` is not present volume, err = ParseVols(komposeObject.ServiceConfigs[svcName].VolList, svcName) if err != nil { return nil, errors.Wrapf(err, "error generating current volumes") } } return } // checkVolDependent returns false if dependent volume is present func checkVolDependent(dv kobject.Volumes, volume []kobject.Volumes) bool { for _, vol := range volume { if vol.PVCName == dv.PVCName { return false } } return true } // ParseVols parse volumes func ParseVols(volNames []string, svcName string) ([]kobject.Volumes, error) { var volumes []kobject.Volumes var err error for i, vn := range volNames { var v kobject.Volumes v.VolumeName, v.Host, v.Container, v.Mode, err = transformer.ParseVolume(vn) if err != nil { return nil, errors.Wrapf(err, "could not parse volume %q: %v", vn, err) } v.VolumeName = normalizeVolumes(v.VolumeName) v.SvcName = svcName v.MountPath = fmt.Sprintf("%s:%s", v.Host, v.Container) v.PVCName = fmt.Sprintf("%s-claim%d", v.SvcName, i) volumes = append(volumes, v) } return volumes, nil } // for dependent volumes, returns true and the respective volume if mountpath are same func getVol(toFind kobject.Volumes, Vols []kobject.Volumes) (bool, kobject.Volumes) { for _, dv := range Vols { if toFind.MountPath == dv.MountPath { return true, dv } } return false, kobject.Volumes{} } func getVolumeLabels(name string, volumes *types.Volumes) (string, string) { size, selector := "", "" if volume, ok := (*volumes)[name]; ok { for key, value := range volume.Labels { if key == "kompose.volume.size" { size = value } else if key == "kompose.volume.selector" { selector = value } } } return size, selector } // getGroupAdd will return group in int64 format func getGroupAdd(group []string) ([]int64, error) { var groupAdd []int64 for _, i := range group { j, err := strconv.Atoi(i) if err != nil { return nil, errors.Wrap(err, "unable to get group_add key") } groupAdd = append(groupAdd, int64(j)) } return groupAdd, nil } ================================================ FILE: pkg/loader/compose/compose_test.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package compose import ( "fmt" "os" "reflect" "strings" "testing" "time" "github.com/compose-spec/compose-go/v2/types" "github.com/google/go-cmp/cmp" "github.com/kubernetes/kompose/pkg/kobject" "github.com/pkg/errors" api "k8s.io/api/core/v1" ) func durationTypesPtr(value time.Duration) *types.Duration { target := types.Duration(value) return &target } func TestParseHealthCheck(t *testing.T) { helperValue := uint64(2) type input struct { healthCheck types.HealthCheckConfig labels types.Labels } testCases := map[string]struct { input input expected kobject.HealthCheck }{ "Exec": { input: input{ healthCheck: types.HealthCheckConfig{ Test: []string{"CMD-SHELL", "echo", "foobar"}, Timeout: durationTypesPtr(1 * time.Second), Interval: durationTypesPtr(2 * time.Second), Retries: &helperValue, StartPeriod: durationTypesPtr(3 * time.Second), }, }, // CMD-SHELL or SHELL is included Test within docker/cli, thus we remove the first value in Test expected: kobject.HealthCheck{ Test: []string{"echo", "foobar"}, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, "HTTPGet": { input: input{ healthCheck: types.HealthCheckConfig{ Timeout: durationTypesPtr(1 * time.Second), Interval: durationTypesPtr(2 * time.Second), Retries: &helperValue, StartPeriod: durationTypesPtr(3 * time.Second), }, labels: types.Labels{ "kompose.service.healthcheck.liveness.http_get_path": "/health", "kompose.service.healthcheck.liveness.http_get_port": "8080", }, }, expected: kobject.HealthCheck{ HTTPPath: "/health", HTTPPort: 8080, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, "TCPSocket": { input: input{ healthCheck: types.HealthCheckConfig{ Timeout: durationTypesPtr(1 * time.Second), Interval: durationTypesPtr(2 * time.Second), Retries: &helperValue, StartPeriod: durationTypesPtr(3 * time.Second), }, labels: types.Labels{ "kompose.service.healthcheck.liveness.tcp_port": "8080", }, }, expected: kobject.HealthCheck{ TCPPort: 8080, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, } for name, testCase := range testCases { t.Log("Test case:", name) output, err := parseHealthCheck(testCase.input.healthCheck, testCase.input.labels) if err != nil { t.Errorf("Unable to convert HealthCheckConfig: %s", err) } if !reflect.DeepEqual(output, testCase.expected) { t.Errorf("Structs are not equal, expected: %v, output: %v", testCase.expected, output) } } } func TestParseHealthCheckReadiness(t *testing.T) { testCases := map[string]struct { input types.Labels expected kobject.HealthCheck }{ "Exec": { input: types.Labels{ "kompose.service.healthcheck.readiness.test": "echo foobar", "kompose.service.healthcheck.readiness.timeout": "1s", "kompose.service.healthcheck.readiness.interval": "2s", "kompose.service.healthcheck.readiness.retries": "2", "kompose.service.healthcheck.readiness.start_period": "3s", }, expected: kobject.HealthCheck{ Test: []string{"echo", "foobar"}, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, "HTTPGet": { input: types.Labels{ "kompose.service.healthcheck.readiness.http_get_path": "/ready", "kompose.service.healthcheck.readiness.http_get_port": "8080", "kompose.service.healthcheck.readiness.timeout": "1s", "kompose.service.healthcheck.readiness.interval": "2s", "kompose.service.healthcheck.readiness.retries": "2", "kompose.service.healthcheck.readiness.start_period": "3s", }, expected: kobject.HealthCheck{ HTTPPath: "/ready", HTTPPort: 8080, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, "TCPSocket": { input: types.Labels{ "kompose.service.healthcheck.readiness.tcp_port": "8080", "kompose.service.healthcheck.readiness.timeout": "1s", "kompose.service.healthcheck.readiness.interval": "2s", "kompose.service.healthcheck.readiness.retries": "2", "kompose.service.healthcheck.readiness.start_period": "3s", }, expected: kobject.HealthCheck{ TCPPort: 8080, Timeout: 1, Interval: 2, Retries: 2, StartPeriod: 3, }, }, } for name, testCase := range testCases { t.Log("Test case:", name) output, err := parseHealthCheckReadiness(testCase.input) if err != nil { t.Errorf("Unable to convert HealthCheckConfig: %s", err) } if !reflect.DeepEqual(output, testCase.expected) { t.Errorf("Structs are not equal, expected: %v, output: %v", testCase.expected, output) } } } func TestLoadV3Volumes(t *testing.T) { vol := types.ServiceVolumeConfig{ Type: "volume", Source: "/tmp/foobar", Target: "/tmp/foobar", ReadOnly: true, } volumes := []types.ServiceVolumeConfig{vol} output := loadVolumes(volumes) expected := "/tmp/foobar:/tmp/foobar:ro" if output[0] != expected { t.Errorf("Expected %s, got %s", expected, output[0]) } } func TestLoadV3Ports(t *testing.T) { for _, tt := range []struct { desc string ports []types.ServicePortConfig expose []string want []kobject.Ports }{ { desc: "ports with expose", ports: []types.ServicePortConfig{{Target: 80, Published: "80", Protocol: string(api.ProtocolTCP)}}, expose: []string{"80", "8080"}, want: []kobject.Ports{ {HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 8080, Protocol: string(api.ProtocolTCP)}, }, }, { desc: "exposed port including /protocol", ports: []types.ServicePortConfig{{Target: 80, Published: "80", Protocol: string(api.ProtocolTCP)}}, expose: []string{"80/udp"}, want: []kobject.Ports{ {HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 80, Protocol: string(api.ProtocolUDP)}, }, }, } { t.Run(tt.desc, func(t *testing.T) { got := loadPorts(tt.ports, tt.expose) if diff := cmp.Diff(tt.want, got); diff != "" { t.Errorf("loadV3Ports() mismatch (-want +got):\n%s", diff) } }) } } // Test if service types are parsed properly on user input // give a service type and expect correct input func TestHandleServiceType(t *testing.T) { tests := []struct { labelValue string serviceType string }{ {"NodePort", "NodePort"}, {"nodeport", "NodePort"}, {"LoadBalancer", "LoadBalancer"}, {"loadbalancer", "LoadBalancer"}, {"ClusterIP", "ClusterIP"}, {"clusterip", "ClusterIP"}, {"", "ClusterIP"}, } for _, tt := range tests { result, err := handleServiceType(tt.labelValue) if err != nil { t.Error(errors.Wrap(err, "handleServiceType failed")) } if result != tt.serviceType { t.Errorf("Expected %q, got %q", tt.serviceType, result) } } } // Test loading of ports func TestLoadPorts(t *testing.T) { portWithIPAddress, _ := types.ParsePortConfig("127.0.0.1:80:80/tcp") portWithoutIPAddress, _ := types.ParsePortConfig("80:80/tcp") portWithoutProtocol, _ := types.ParsePortConfig("80:80") singlePort, _ := types.ParsePortConfig("80") singlePortsRange, _ := types.ParsePortConfig("3000-3002") targetAndContainerPortsRange, _ := types.ParsePortConfig("3000-3002:5000-5002") targetAndContainerPortsRangeWithIPAddress, _ := types.ParsePortConfig("127.0.0.1:3000-3002:5000-5002") port3000, _ := types.ParsePortConfig("3000") tests := []struct { ports []types.ServicePortConfig expose []string want []kobject.Ports }{ { ports: portWithIPAddress, want: []kobject.Ports{ {HostIP: "127.0.0.1", HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, }, }, { ports: portWithoutIPAddress, want: []kobject.Ports{ {HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, }, }, { ports: portWithoutProtocol, want: []kobject.Ports{ {HostPort: 80, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, }, }, { ports: singlePort, want: []kobject.Ports{ {ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, }, }, { ports: singlePortsRange, want: []kobject.Ports{ {ContainerPort: 3000, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 3001, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 3002, Protocol: string(api.ProtocolTCP)}, }, }, { ports: targetAndContainerPortsRange, want: []kobject.Ports{ {HostPort: 3000, ContainerPort: 5000, Protocol: string(api.ProtocolTCP)}, {HostPort: 3001, ContainerPort: 5001, Protocol: string(api.ProtocolTCP)}, {HostPort: 3002, ContainerPort: 5002, Protocol: string(api.ProtocolTCP)}, }, }, { ports: targetAndContainerPortsRangeWithIPAddress, want: []kobject.Ports{ {HostIP: "127.0.0.1", HostPort: 3000, ContainerPort: 5000, Protocol: string(api.ProtocolTCP)}, {HostIP: "127.0.0.1", HostPort: 3001, ContainerPort: 5001, Protocol: string(api.ProtocolTCP)}, {HostIP: "127.0.0.1", HostPort: 3002, ContainerPort: 5002, Protocol: string(api.ProtocolTCP)}, }, }, { ports: append(append([]types.ServicePortConfig{}, singlePort...), port3000...), want: []kobject.Ports{ {HostPort: 0, ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, {HostPort: 0, ContainerPort: 3000, Protocol: string(api.ProtocolTCP)}, }, }, { ports: append(append([]types.ServicePortConfig{}, singlePort...), port3000...), expose: []string{"80", "8080"}, want: []kobject.Ports{ {ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 3000, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 80, Protocol: string(api.ProtocolTCP)}, {ContainerPort: 8080, Protocol: string(api.ProtocolTCP)}, }, }, } for _, tt := range tests { t.Run(fmt.Sprintf("port=%q,expose=%q", tt.ports, tt.expose), func(t *testing.T) { got := loadPorts(tt.ports, tt.expose) if diff := cmp.Diff(tt.want, got); diff != "" { t.Errorf("loadPorts() mismatch (-want +got):\n%s", diff) } }) } } func TestLoadEnvVar(t *testing.T) { ev1 := []string{"foo=bar"} rs1 := kobject.EnvVar{ Name: "foo", Value: "bar", } ev2 := []string{"foo:bar"} rs2 := kobject.EnvVar{ Name: "foo", Value: "bar", } ev3 := []string{"foo"} rs3 := kobject.EnvVar{ Name: "foo", Value: "", } ev4 := []string{"osfoo"} rs4 := kobject.EnvVar{ Name: "osfoo", Value: "osbar", } ev5 := []string{"foo:bar=foobar"} rs5 := kobject.EnvVar{ Name: "foo", Value: "bar=foobar", } ev6 := []string{"foo=foo:bar"} rs6 := kobject.EnvVar{ Name: "foo", Value: "foo:bar", } ev7 := []string{"foo:"} rs7 := kobject.EnvVar{ Name: "foo", Value: "", } ev8 := []string{"foo="} rs8 := kobject.EnvVar{ Name: "foo", Value: "", } tests := []struct { envvars []string results kobject.EnvVar }{ {ev1, rs1}, {ev2, rs2}, {ev3, rs3}, {ev4, rs4}, {ev5, rs5}, {ev6, rs6}, {ev7, rs7}, {ev8, rs8}, } os.Setenv("osfoo", "osbar") for _, tt := range tests { result := loadEnvVars(tt.envvars) if result[0] != tt.results { t.Errorf("Expected %q, got %q", tt.results, result[0]) } } } func TestParseEnvFiles(t *testing.T) { tests := []struct { service types.ServiceConfig want []string }{ {service: types.ServiceConfig{ Name: "baz", Image: "foo/baz", EnvFiles: []types.EnvFile{ { Path: "", Required: false, }, { Path: "foo", Required: false, }, { Path: "bar", Required: true, }, }, }, want: []string{"", "foo", "bar"}, }, { service: types.ServiceConfig{ Name: "baz", Image: "foo/baz", EnvFiles: []types.EnvFile{}, }, want: []string{}, }, } for _, tt := range tests { sc := kobject.ServiceConfig{ EnvFile: []string{}, } parseEnvFiles(&tt.service, &sc) if !reflect.DeepEqual(sc.EnvFile, tt.want) { t.Errorf("Expected %q, got %q", tt.want, sc.EnvFile) } } } // TestUnsupportedKeys test checkUnsupportedKey function with various // docker-compose projects func TestUnsupportedKeys(t *testing.T) { // create project that will be used in test cases projectWithNetworks := &types.Project{ Networks: types.Networks{ "foo": types.NetworkConfig{ Name: "foo", Driver: "bridge", }, }, Services: types.Services{ "foo": types.ServiceConfig{ Name: "foo", Image: "foo/bar", Build: &types.BuildConfig{ Context: "./build", }, Hostname: "localhost", Ports: []types.ServicePortConfig{}, // test empty array Networks: map[string]*types.ServiceNetworkConfig{ "net1": {}, }, }, "bar": types.ServiceConfig{ Name: "bar", Image: "bar/foo", Build: &types.BuildConfig{ Context: "./build", }, Hostname: "localhost", Ports: []types.ServicePortConfig{}, // test empty array Networks: map[string]*types.ServiceNetworkConfig{ "net1": {}, }, }, }, Volumes: types.Volumes{ "foo": types.VolumeConfig{ Name: "foo", Driver: "storage", }, }, } projectWithDefaultNetwork := &types.Project{ Services: types.Services{ "foo": types.ServiceConfig{ Networks: map[string]*types.ServiceNetworkConfig{ "default": {}, }, }, }, } // define all test cases for checkUnsupportedKey function testCases := map[string]struct { composeProject *types.Project expectedUnsupportedKeys []string }{ "With Networks (service and root level)": { projectWithNetworks, //root level network and network are now supported" []string{"root level volumes"}, }, "Default root level Network": { projectWithDefaultNetwork, []string(nil), }, } for name, test := range testCases { t.Log("Test case:", name) keys := checkUnsupportedKey(test.composeProject) if !reflect.DeepEqual(keys, test.expectedUnsupportedKeys) { t.Errorf("ERROR: Expecting unsupported keys: ['%s']. Got: ['%s']", strings.Join(test.expectedUnsupportedKeys, "', '"), strings.Join(keys, "', '")) } } } func TestNormalizeServiceNames(t *testing.T) { testCases := []struct { composeServiceName string normalizedServiceName string }{ {"foo_bar", "foo-bar"}, {"foo", "foo"}, {"foo.bar", "foo.bar"}, //{"", ""}, } for _, testCase := range testCases { returnValue := normalizeServiceNames(testCase.composeServiceName) if returnValue != testCase.normalizedServiceName { t.Logf("Expected %q, got %q", testCase.normalizedServiceName, returnValue) } } } func TestNormalizeNetworkNames(t *testing.T) { testCases := []struct { composeNetworkName string normalizedNetworkName string }{ {"foo_bar", "foo-bar"}, {"foo", "foo"}, {"FOO", "foo"}, {"foo.bar", "foo.bar"}, //{"", ""}, } for _, testCase := range testCases { returnValue, err := normalizeNetworkNames(testCase.composeNetworkName) if err != nil { t.Log("Unexpected error, got ", err) } if returnValue != testCase.normalizedNetworkName { t.Logf("Expected %q, got %q", testCase.normalizedNetworkName, returnValue) } } } func TestCheckPlacementCustomLabels(t *testing.T) { placement := types.Placement{ Constraints: []string{ "node.labels.something == anything", "node.labels.monitor != xxx", }, Preferences: []types.PlacementPreferences{ {Spread: "node.labels.zone"}, {Spread: "foo"}, {Spread: "node.labels.ssd"}, }, } output := loadPlacement(placement) expected := kobject.Placement{ PositiveConstraints: map[string]string{ "something": "anything", }, NegativeConstraints: map[string]string{ "monitor": "xxx", }, Preferences: []string{ "zone", "ssd", }, } checkConstraints(t, "positive", output.PositiveConstraints, expected.PositiveConstraints) checkConstraints(t, "negative", output.NegativeConstraints, expected.NegativeConstraints) if len(output.Preferences) != len(expected.Preferences) { t.Errorf("preferences len is not equal, expected %d, got %d", len(expected.Preferences), len(output.Preferences)) } for i := range output.Preferences { if output.Preferences[i] != expected.Preferences[i] { t.Errorf("preference is not equal, expected %s, got %s", expected.Preferences[i], output.Preferences[i]) } } } func checkConstraints(t *testing.T, caseName string, output, expected map[string]string) { t.Log("Test case:", caseName) if len(output) != len(expected) { t.Errorf("constraints len is not equal, expected %d, got %d", len(expected), len(output)) } for key := range output { if output[key] != expected[key] { t.Errorf("%s constraint is not equal, expected %s, got %s", key, expected[key], output[key]) } } } func Test_parseKomposeLabels(t *testing.T) { service := kobject.ServiceConfig{ Name: "name", ContainerName: "containername", Image: "image", Labels: nil, Annotations: map[string]string{"abc": "def"}, Restart: "always", } type args struct { labels types.Labels serviceConfig *kobject.ServiceConfig } tests := []struct { name string args args expected *kobject.ServiceConfig }{ { name: "override with overriding", args: args{ labels: types.Labels{ LabelNameOverride: "overriding", }, serviceConfig: &service, }, expected: &kobject.ServiceConfig{ Name: "overriding", }, }, { name: "override", args: args{ labels: types.Labels{ LabelNameOverride: "overriding-resource-name", }, serviceConfig: &service, }, expected: &kobject.ServiceConfig{ Name: "overriding-resource-name", }, }, { name: "hyphen in the middle", args: args{ labels: types.Labels{ LabelNameOverride: "overriding_resource-name", }, serviceConfig: &service, }, expected: &kobject.ServiceConfig{ Name: "overriding-resource-name", }, }, { name: "hyphen in the middle with mays", args: args{ labels: types.Labels{ LabelNameOverride: "OVERRIDING_RESOURCE-NAME", }, serviceConfig: &service, }, expected: &kobject.ServiceConfig{ Name: "overriding-resource-name", }, }, // This is a corner case that is expected to fail because // it does not account for scenarios where the string // starts or ends with a '-' or any other character // this test will fail with current tests // { // name: "Add a prefix with a dash at the start and end, with a hyphen in the middle.", // args: args{ // labels: types.Labels{ // LabelNameOverride: "-OVERRIDING_RESOURCE-NAME-", // }, // serviceConfig: &service, // }, // expected: &kobject.ServiceConfig{ // Name: "overriding-resource-name", // }, // }, // not fail { name: "Add a prefix with a dash at the start and end, with a hyphen in the middle.", args: args{ labels: types.Labels{ LabelNameOverride: "-OVERRIDING_RESOURCE-NAME-", }, serviceConfig: &service, }, expected: &kobject.ServiceConfig{ Name: "-overriding-resource-name-", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := parseKomposeLabels(tt.args.labels, tt.args.serviceConfig); err != nil { t.Errorf("parseKomposeLabels(): %v", err) } if tt.expected.Name != tt.args.serviceConfig.Name { t.Errorf("Name are not equal, expected: %v, output: %v", tt.expected.Name, tt.args.serviceConfig.Name) } }) } } ================================================ FILE: pkg/loader/compose/utils.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package compose import ( "io" "os" "regexp" "strings" "github.com/compose-spec/compose-go/v2/types" "github.com/kubernetes/kompose/pkg/kobject" "github.com/pkg/errors" api "k8s.io/api/core/v1" ) const ( // LabelServiceType defines the type of service to be created LabelServiceType = "kompose.service.type" // LabelServiceExternalTrafficPolicy defines the external policy traffic of service to be created LabelServiceExternalTrafficPolicy = "kompose.service.external-traffic-policy" // LabelServiceGroup defines the group of services in a single pod LabelServiceGroup = "kompose.service.group" // LabelNodePortPort defines the port value for NodePort service LabelNodePortPort = "kompose.service.nodeport.port" // LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not LabelServiceExpose = "kompose.service.expose" // LabelServiceExposeTLSSecret provides the name of the TLS secret to use with the Kubernetes ingress controller LabelServiceExposeTLSSecret = "kompose.service.expose.tls-secret" // LabelServiceExposeIngressClassName provides the name of ingress class to use with the Kubernetes ingress controller LabelServiceExposeIngressClassName = "kompose.service.expose.ingress-class-name" // LabelServiceAccountName defines the service account name to provide the credential info of the pod. LabelServiceAccountName = "kompose.serviceaccount-name" // LabelControllerType defines the type of controller to be created LabelControllerType = "kompose.controller.type" // LabelImagePullSecret defines a secret name for kubernetes ImagePullSecrets LabelImagePullSecret = "kompose.image-pull-secret" // LabelImagePullPolicy defines Kubernetes PodSpec imagePullPolicy. LabelImagePullPolicy = "kompose.image-pull-policy" // HealthCheckReadinessDisable defines readiness health check disable HealthCheckReadinessDisable = "kompose.service.healthcheck.readiness.disable" // HealthCheckReadinessTest defines readiness health check test HealthCheckReadinessTest = "kompose.service.healthcheck.readiness.test" // HealthCheckReadinessInterval defines readiness health check interval HealthCheckReadinessInterval = "kompose.service.healthcheck.readiness.interval" // HealthCheckReadinessTimeout defines readiness health check timeout HealthCheckReadinessTimeout = "kompose.service.healthcheck.readiness.timeout" // HealthCheckReadinessRetries defines readiness health check retries HealthCheckReadinessRetries = "kompose.service.healthcheck.readiness.retries" // HealthCheckReadinessStartPeriod defines readiness health check start period HealthCheckReadinessStartPeriod = "kompose.service.healthcheck.readiness.start_period" // HealthCheckReadinessHTTPGetPath defines readiness health check HttpGet path HealthCheckReadinessHTTPGetPath = "kompose.service.healthcheck.readiness.http_get_path" // HealthCheckReadinessHTTPGetPort defines readiness health check HttpGet port HealthCheckReadinessHTTPGetPort = "kompose.service.healthcheck.readiness.http_get_port" // HealthCheckReadinessTCPPort defines readiness health check tcp port HealthCheckReadinessTCPPort = "kompose.service.healthcheck.readiness.tcp_port" // HealthCheckLivenessHTTPGetPath defines liveness health check HttpGet path HealthCheckLivenessHTTPGetPath = "kompose.service.healthcheck.liveness.http_get_path" // HealthCheckLivenessHTTPGetPort defines liveness health check HttpGet port HealthCheckLivenessHTTPGetPort = "kompose.service.healthcheck.liveness.http_get_port" // HealthCheckLivenessTCPPort defines liveness health check tcp port HealthCheckLivenessTCPPort = "kompose.service.healthcheck.liveness.tcp_port" // ServiceTypeHeadless ... ServiceTypeHeadless = "Headless" // LabelSecurityContextFsGroup defines the pod FsGroup LabelSecurityContextFsGroup = "kompose.security-context.fsgroup" // LabelContainerVolumeSubpath defines the volume mount subpath inside container LabelContainerVolumeSubpath = "kompose.volume.subpath" // LabelCronJobSchedule defines the cron job schedule LabelCronJobSchedule = "kompose.cronjob.schedule" // LabelCronJobConcurrencyPolicy defines the cron job concurrency policy LabelCronJobConcurrencyPolicy = "kompose.cronjob.concurrency_policy" // LabelCronJobBackoffLimit defines the job backoff limit LabelCronJobBackoffLimit = "kompose.cronjob.backoff_limit" // LabelInitContainerName defines name resource LabelInitContainerName = "kompose.init.containers.name" // LabelInitContainerImage defines image to pull LabelInitContainerImage = "kompose.init.containers.image" // LabelInitContainerCommand defines commands LabelInitContainerCommand = "kompose.init.containers.command" // LabelHpaMinReplicas defines min pod replicas LabelHpaMinReplicas = "kompose.hpa.replicas.min" // LabelHpaMaxReplicas defines max pod replicas LabelHpaMaxReplicas = "kompose.hpa.replicas.max" // LabelHpaCpu defines scaling decisions based on CPU utilization LabelHpaCPU = "kompose.hpa.cpu" // LabelHpaMemory defines scaling decisions based on memory utilization LabelHpaMemory = "kompose.hpa.memory" // LabelNameOverride defines the override resource name LabelNameOverride = "kompose.service.name_override" // LabelExposeContainerToHost defines whether to expose container to host or not using hostPort LabelExposeContainerToHost = "kompose.controller.port.expose" ) // load environment variables from compose file func loadEnvVars(envars []string) []kobject.EnvVar { envs := []kobject.EnvVar{} for _, e := range envars { character := "" equalPos := strings.Index(e, "=") colonPos := strings.Index(e, ":") switch { case equalPos == -1 && colonPos == -1: character = "" case equalPos == -1 && colonPos != -1: character = ":" case equalPos != -1 && colonPos == -1: character = "=" case equalPos != -1 && colonPos != -1: if equalPos > colonPos { character = ":" } else { character = "=" } } if character == "" { envs = append(envs, kobject.EnvVar{ Name: e, Value: os.Getenv(e), }) } else { values := strings.SplitN(e, character, 2) // try to get value from os env if values[1] == "" { values[1] = os.Getenv(values[0]) } envs = append(envs, kobject.EnvVar{ Name: values[0], Value: values[1], }) } } return envs } func handleServiceType(ServiceType string) (string, error) { switch strings.ToLower(ServiceType) { case "", "clusterip": return string(api.ServiceTypeClusterIP), nil case "nodeport": return string(api.ServiceTypeNodePort), nil case "loadbalancer": return string(api.ServiceTypeLoadBalancer), nil case "headless": return ServiceTypeHeadless, nil default: return "", errors.New("Unknown value " + ServiceType + " , supported values are 'nodeport, clusterip, headless or loadbalancer'") } } func handleServiceExternalTrafficPolicy(ServiceExternalTrafficPolicyType string) (string, error) { switch strings.ToLower(ServiceExternalTrafficPolicyType) { case "", "cluster": return string(api.ServiceExternalTrafficPolicyTypeCluster), nil case "local": return string(api.ServiceExternalTrafficPolicyTypeLocal), nil default: return "", errors.New("Unknown value " + ServiceExternalTrafficPolicyType + " , supported values are 'local, cluster'") } } func normalizeContainerNames(svcName string) string { return strings.ToLower(svcName) } func normalizeServiceNames(svcName string) string { re := regexp.MustCompile("[._]") return strings.ToLower(re.ReplaceAllString(svcName, "-")) } func normalizeVolumes(svcName string) string { return strings.Replace(svcName, "_", "-", -1) } func normalizeNetworkNames(netName string) (string, error) { netval := strings.ToLower(strings.Replace(netName, "_", "-", -1)) regString := "[^A-Za-z0-9.-]+" reg, err := regexp.Compile(regString) if err != nil { return "", err } netval = reg.ReplaceAllString(netval, "") return netval, nil } // ReadFile read data from file or stdin func ReadFile(fileName string) ([]byte, error) { if fileName == "-" { if StdinData == nil { data, err := io.ReadAll(os.Stdin) StdinData = data return data, err } return StdinData, nil } return os.ReadFile(fileName) } // Choose normalized name from resource in case exist LabelNameOverride // from label func parseResourceName(resourceName string, labels types.Labels) string { // Opted to use normalizeContainerNames over normalizeServiceNames // as in tests, normalization is only to lowercase. normalizedName := normalizeContainerNames(resourceName) if labelValue, exist := labels[LabelNameOverride]; exist { normalizedName = normalizeContainerNames(labelValue) } return normalizedName } ================================================ FILE: pkg/loader/loader.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package loader import ( "fmt" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader/compose" ) // Loader interface defines loader that loads files and converts it to kobject representation type Loader interface { LoadFile(files []string, profiles []string, noInterpolate bool) (kobject.KomposeObject, error) ///Name() string } // GetLoader returns loader for given format func GetLoader(format string) (Loader, error) { if format != "compose" { return nil, fmt.Errorf("input file format %s is not supported", format) } return new(compose.Compose), nil } ================================================ FILE: pkg/snap/snapcraft.yaml ================================================ name: kompose summary: Go from Docker Compose to Kubernetes. description:Kompose is a tool to help users who are familiar with docker-compose move to Kubernetes. kompose takes a Docker Compose file and translates it into Kubernetes resources. grade: stable confinement: classic architectures: - build-on: i386 - build-on: amd64 - build-on: armhf - build-on: arm64 apps: kompose: command: bin/kompose plugs: - home - network - docker - removable-media parts: kompose: plugin: nil source: https://github.com/kubernetes/kompose.git source-type: git override-pull: | git clone https://github.com/kubernetes/kompose.git src/github.com/kubernetes/kompose cd src/github.com/kubernetes/kompose last_committed_tag="$(git describe --tags --abbrev=0)" last_committed_tag_ver="$(echo ${last_committed_tag} | sed 's/v//')" last_released_tag="$(snap info $SNAPCRAFT_PROJECT_NAME | awk '$1 == "beta:" { print $2 }')" # If the latest tag from the upstream project has not been released to # beta, build that tag instead of master. if [ "${last_committed_tag_ver}" != "${last_released_tag}" ]; then git fetch git checkout "${last_committed_tag}" fi snapcraftctl set-version "$(git describe --tags | sed 's/v//')" override-build: | export GOPATH=$PWD cd src/github.com/kubernetes/kompose env CGO_ENABLED=0 GOOS=linux \ go build --ldflags "-s -w \ -X 'github.com/kubernetes/kompose/version.GitCommit=$(git rev-list -1 HEAD)' \ -X 'github.com/kubernetes/kompose/version.Version=$(git describe --tags --abbrev=0)'" \ -a -installsuffix cgo -o $SNAPCRAFT_PART_INSTALL/bin/kompose build-snaps: - go build-packages: - git - sed ================================================ FILE: pkg/testutils/git.go ================================================ package testutils import ( "fmt" "os" "os/exec" "testing" ) // NewCommand TODO: comment func NewCommand(cmd string) *exec.Cmd { return exec.Command("sh", "-c", cmd) } // CreateLocalDirectory TODO: comment func CreateLocalDirectory(t *testing.T) string { dir, err := os.MkdirTemp(os.TempDir(), "kompose-test-") if err != nil { t.Fatal(err) } return dir } // CreateLocalGitDirectory TODO: comment func CreateLocalGitDirectory(t *testing.T) string { dir := CreateLocalDirectory(t) cmd := NewCommand( `git init && git config user.email "you@example.com" && git config user.name "Your Name" && touch README && git add README && git commit --no-gpg-sign -m 'testcommit'`) cmd.Dir = dir _, err := cmd.Output() if err != nil { t.Logf("create local git dir: %v", err) t.Fatal(err) } return dir } // SetGitRemote TODO: comment func SetGitRemote(t *testing.T, dir string, remote string, remoteURL string) { cmd := NewCommand(fmt.Sprintf("git remote add %s %s", remote, remoteURL)) cmd.Dir = dir _, err := cmd.Output() if err != nil { t.Logf("set git remote: %v", err) t.Fatal(err) } } // CreateGitRemoteBranch TODO: comment func CreateGitRemoteBranch(t *testing.T, dir string, branch string, remote string) { cmd := NewCommand( fmt.Sprintf(`git checkout -b %s && git config branch.%s.remote %s && git config branch.%s.merge refs/heads/%s`, branch, branch, remote, branch, branch)) cmd.Dir = dir _, err := cmd.Output() if err != nil { t.Logf("create git branch: %v", err) t.Fatal(err) } } // CreateSubdir TODO: comment func CreateSubdir(t *testing.T, dir string, subdir string) { cmd := NewCommand(fmt.Sprintf("mkdir -p %s", subdir)) cmd.Dir = dir _, err := cmd.Output() if err != nil { t.Fatal(err) } } ================================================ FILE: pkg/testutils/kubernetes.go ================================================ package testutils import ( "errors" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) // CheckForHeadless is helper function for tests. // It checks if all Services in objects are Headless Services and if there is at least one such Services. func CheckForHeadless(objects []runtime.Object) error { serviceCreated := false for _, obj := range objects { if svc, ok := obj.(*v1.Service); ok { serviceCreated = true // Check if it is a headless services if svc.Spec.ClusterIP != "None" { return errors.New("this is not a Headless services") } } } if !serviceCreated { return errors.New("no Service created") } return nil } // CheckForHealthCheckLivenessAndReadiness check if has liveness and readiness in healthcheck configured. func CheckForHealthCheckLivenessAndReadiness(objects []runtime.Object) error { serviceCreated := false for _, obj := range objects { if deployment, ok := obj.(*appsv1.Deployment); ok { serviceCreated = true // Check if it is a headless services if deployment.Spec.Template.Spec.Containers[0].ReadinessProbe == nil { return errors.New("there is not a ReadinessProbe") } if deployment.Spec.Template.Spec.Containers[0].LivenessProbe == nil { return errors.New("there is not a LivenessGate") } } } if !serviceCreated { return errors.New("no Service created") } return nil } ================================================ FILE: pkg/transformer/kubernetes/k8sutils.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package kubernetes import ( "bytes" "encoding/json" "fmt" "os" "path" "path/filepath" "reflect" "regexp" "sort" "strconv" "strings" "text/template" "time" "github.com/compose-spec/compose-go/v2/dotenv" "github.com/compose-spec/compose-go/v2/types" "github.com/joho/godotenv" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader/compose" "github.com/kubernetes/kompose/pkg/transformer" deployapi "github.com/openshift/api/apps/v1" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" appsv1 "k8s.io/api/apps/v1" hpa "k8s.io/api/autoscaling/v2beta2" api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) // Default values for Horizontal Pod Autoscaler (HPA) const ( DefaultMinReplicas = 1 DefaultMaxReplicas = 3 DefaultCPUUtilization = 50 DefaultMemoryUtilization = 70 ) // LabelKeys are the keys for HPA related labels in the service var LabelKeys = []string{ compose.LabelHpaCPU, compose.LabelHpaMemory, compose.LabelHpaMinReplicas, compose.LabelHpaMaxReplicas, } type HpaValues struct { MinReplicas int32 MaxReplicas int32 CPUtilization int32 MemoryUtilization int32 } const ( NetworkModeService = "service:" ) type DeploymentMapping struct { SourceDeploymentName string TargetDeploymentName string } /** * Generate Helm Chart configuration */ func generateHelm(dirName string) error { type ChartDetails struct { Name string } details := ChartDetails{dirName} manifestDir := dirName + string(os.PathSeparator) + "templates" dir, err := os.Open(dirName) /* Setup the initial directories/files */ if err == nil { _ = dir.Close() } if err != nil { err = os.Mkdir(dirName, 0755) if err != nil { return err } err = os.Mkdir(manifestDir, 0755) if err != nil { return err } } /* Create the readme file */ readme := "This chart was created by Kompose\n" err = os.WriteFile(dirName+string(os.PathSeparator)+"README.md", []byte(readme), 0644) if err != nil { return err } /* Create the Chart.yaml file */ chart := `name: {{.Name}} description: A generated Helm Chart for {{.Name}} from Skippbox Kompose version: 0.0.1 apiVersion: v2 keywords: - {{.Name}} sources: home: ` t, err := template.New("ChartTmpl").Parse(chart) if err != nil { return errors.Wrap(err, "Failed to generate Chart.yaml template, template.New failed") } var chartData bytes.Buffer _ = t.Execute(&chartData, details) err = os.WriteFile(dirName+string(os.PathSeparator)+"Chart.yaml", chartData.Bytes(), 0644) if err != nil { return err } log.Infof("chart created in %q\n", dirName+string(os.PathSeparator)) return nil } // Check if given path is a directory func isDir(name string) (bool, error) { // Open file to get stat later f, err := os.Open(name) if err != nil { return false, nil } defer f.Close() // Get file attributes and information fileStat, err := f.Stat() if err != nil { return false, errors.Wrap(err, "error retrieving file information, f.Stat failed") } // Check if given path is a directory if fileStat.IsDir() { return true, nil } return false, nil } func getDirName(opt kobject.ConvertOptions) string { dirName := opt.OutFile if dirName == "" { // Let assume all the docker-compose files are in the same directory if opt.CreateChart { filename := opt.InputFiles[0] extension := filepath.Ext(filename) dirName = filename[0 : len(filename)-len(extension)] } else { dirName = "." } } return dirName } // PrintList will take the data converted and decide on the commandline attributes given func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error { var f *os.File dirName := getDirName(opt) log.Debugf("Target Dir: %s", dirName) // Create a directory if "out" ends with "/" and does not exist. if !transformer.Exists(opt.OutFile) && strings.HasSuffix(opt.OutFile, "/") { if err := os.MkdirAll(opt.OutFile, os.ModePerm); err != nil { return errors.Wrap(err, "failed to create a directory") } } // Check if output file is a directory isDirVal, err := isDir(opt.OutFile) if err != nil { return errors.Wrap(err, "isDir failed") } if opt.CreateChart { isDirVal = true } if !isDirVal { f, err = transformer.CreateOutFile(opt.OutFile) if err != nil { return errors.Wrap(err, "transformer.CreateOutFile failed") } if len(opt.OutFile) != 0 { log.Printf("Kubernetes file %q created", opt.OutFile) } defer f.Close() } var files []string // if asked to print to stdout or to put in single file // we will create a list if opt.ToStdout || f != nil { // convert objects to versioned and add them to list if opt.GenerateJSON { return fmt.Errorf("cannot convert to one file while specifying a json output file or stdout option") } for _, object := range objects { versionedObject, err := convertToVersion(object) if err != nil { return err } data, err := marshal(versionedObject, opt.GenerateJSON, opt.YAMLIndent) if err != nil { return fmt.Errorf("error in marshalling the List: %v", err) } // this part add --- which unifies the file data = []byte(fmt.Sprintf("---\n%s", data)) printVal, err := transformer.Print("", dirName, "", data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider) if err != nil { return errors.Wrap(err, "transformer to print to one single file failed") } files = append(files, printVal) } } else { finalDirName := dirName if opt.CreateChart { finalDirName = dirName + string(os.PathSeparator) + "templates" } if err := os.MkdirAll(finalDirName, 0755); err != nil { return err } var file string // create a separate file for each provider for _, v := range objects { versionedObject, err := convertToVersion(v) if err != nil { return err } data, err := marshal(versionedObject, opt.GenerateJSON, opt.YAMLIndent) if err != nil { return err } var typeMeta metav1.TypeMeta var objectMeta metav1.ObjectMeta if us, ok := v.(*unstructured.Unstructured); ok { typeMeta = metav1.TypeMeta{ Kind: us.GetKind(), APIVersion: us.GetAPIVersion(), } objectMeta = metav1.ObjectMeta{ Name: us.GetName(), } } else { val := reflect.ValueOf(v).Elem() // Use reflect to access TypeMeta struct inside runtime.Object. // cast it to correct type - metav1.TypeMeta typeMeta = val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta) // Use reflect to access ObjectMeta struct inside runtime.Object. // cast it to correct type - api.ObjectMeta objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta) } file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider) if err != nil { return errors.Wrap(err, "transformer.Print failed") } files = append(files, file) } } if opt.CreateChart { err = generateHelm(dirName) if err != nil { return errors.Wrap(err, "generateHelm failed") } } return nil } // marshal object runtime.Object and return byte array func marshal(obj runtime.Object, jsonFormat bool, indent int) (data []byte, err error) { // convert data to yaml or json if jsonFormat { data, err = json.MarshalIndent(obj, "", " ") } else { data, err = marshalWithIndent(obj, indent) } if err != nil { data = nil } return } // remove empty map[string]interface{} strings from the object // // Note: this function uses recursion, use it only objects created by the unmarshalled json. // Passing cyclic structures to removeEmptyInterfaces will result in a stack overflow. func removeEmptyInterfaces(obj interface{}) interface{} { switch v := obj.(type) { case []interface{}: for i, val := range v { if valMap, ok := val.(map[string]interface{}); (ok && len(valMap) == 0) || val == nil { v = append(v[:i], v[i+1:]...) } else { v[i] = removeEmptyInterfaces(val) } } return v case map[string]interface{}: for k, val := range v { if valMap, ok := val.(map[string]interface{}); ok { // It is always map[string]interface{} when passed the map[string]interface{} valMap := removeEmptyInterfaces(valMap).(map[string]interface{}) if len(valMap) == 0 { delete(v, k) } } else if val == nil { delete(v, k) } else { processedInterface := removeEmptyInterfaces(val) if valSlice, ok := processedInterface.([]interface{}); ok && len(valSlice) == 0 { delete(v, k) } else { v[k] = processedInterface } } } return v default: return v } } // Convert JSON to YAML. func jsonToYaml(j []byte, spaces int) ([]byte, error) { // Convert the JSON to an object. var jsonObj interface{} // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the // Go JSON library doesn't try to pick the right number type (int, float, // etc.) when unmarshling to interface{}, it just picks float64 // universally. go-yaml does go through the effort of picking the right // number type, so we can preserve number type throughout this process. err := yaml.Unmarshal(j, &jsonObj) if err != nil { return nil, err } jsonObj = removeEmptyInterfaces(jsonObj) var b bytes.Buffer encoder := yaml.NewEncoder(&b) encoder.SetIndent(spaces) if err := encoder.Encode(jsonObj); err != nil { return nil, err } return b.Bytes(), nil // Marshal this object into YAML. // return yaml.Marshal(jsonObj) } func marshalWithIndent(o interface{}, indent int) ([]byte, error) { j, err := json.Marshal(o) if err != nil { return nil, fmt.Errorf("error marshaling into JSON: %s", err.Error()) } y, err := jsonToYaml(j, indent) if err != nil { return nil, fmt.Errorf("error converting JSON to YAML: %s", err.Error()) } return y, nil } // Convert object to versioned object // if groupVersion is empty (metav1.GroupVersion{}), use version from original object (obj) func convertToVersion(obj runtime.Object) (runtime.Object, error) { // ignore unstruct object if _, ok := obj.(*unstructured.Unstructured); ok { return obj, nil } return obj, nil //var version metav1.GroupVersion // //if groupVersion.Empty() { // objectVersion := obj.GetObjectKind().GroupVersionKind() // version = metav1.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} //} else { // version = groupVersion //} //convertedObject, err := api.Scheme.ConvertToVersion(obj, version) //if err != nil { // return nil, err //} //return convertedObject, nil } // PortsExist checks if service has ports defined func (k *Kubernetes) PortsExist(service kobject.ServiceConfig) bool { return len(service.Port) != 0 } func (k *Kubernetes) initSvcObject(name string, service kobject.ServiceConfig, ports []api.ServicePort) *api.Service { svc := k.InitSvc(name, service) // special case, only for loaderbalancer type svc.Name = name svc.Spec.Selector = transformer.ConfigLabels(service.Name) svc.Spec.Ports = ports svc.Spec.Type = api.ServiceType(service.ServiceType) // Configure annotations annotations := transformer.ConfigAnnotations(service) svc.ObjectMeta.Annotations = annotations return svc } // CreateLBService creates a k8s Load Balancer Service func (k *Kubernetes) CreateLBService(name string, service kobject.ServiceConfig) []*api.Service { var svcs []*api.Service tcpPorts, udpPorts := k.ConfigLBServicePorts(service) if tcpPorts != nil { svc := k.initSvcObject(name+"-tcp", service, tcpPorts) svcs = append(svcs, svc) } if udpPorts != nil { svc := k.initSvcObject(name+"-udp", service, udpPorts) svcs = append(svcs, svc) } return svcs } // CreateService creates a k8s service func (k *Kubernetes) CreateService(name string, service kobject.ServiceConfig) *api.Service { svc := k.InitSvc(name, service) // Configure the service ports. servicePorts := k.ConfigServicePorts(service) svc.Spec.Ports = servicePorts if service.ServiceType == "Headless" { svc.Spec.Type = api.ServiceTypeClusterIP svc.Spec.ClusterIP = "None" } else { svc.Spec.Type = api.ServiceType(service.ServiceType) } // Configure annotations annotations := transformer.ConfigAnnotations(service) svc.ObjectMeta.Annotations = annotations return svc } // CreateHeadlessService creates a k8s headless service. // This is used for docker-compose services without ports. For such services we can't create regular Kubernetes Service. // and without Service Pods can't find each other using DNS names. // Instead of regular Kubernetes Service we create Headless Service. DNS of such service points directly to Pod IP address. // You can find more about Headless Services in Kubernetes documentation https://kubernetes.io/docs/user-guide/services/#headless-services func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceConfig) *api.Service { svc := k.InitSvc(name, service) var servicePorts []api.ServicePort // Configure a dummy port: https://github.com/kubernetes/kubernetes/issues/32766. servicePorts = append(servicePorts, api.ServicePort{ Name: "headless", Port: 55555, }) svc.Spec.Ports = servicePorts svc.Spec.ClusterIP = "None" // Configure annotations annotations := transformer.ConfigAnnotations(service) svc.ObjectMeta.Annotations = annotations return svc } // UpdateKubernetesObjectsMultipleContainers method updates the kubernetes objects with the necessary data func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, objects *[]runtime.Object, podSpec PodSpec, opt kobject.ConvertOptions) error { // Configure annotations annotations := transformer.ConfigAnnotations(service) // fillTemplate fills the pod template with the value calculated from config fillTemplate := func(template *api.PodTemplateSpec) error { // We will ONLY add config labels with network if we actually // passed in --generate-network-policies to the kompose command if opt.GenerateNetworkPolicies { template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network) } else { template.ObjectMeta.Labels = transformer.ConfigLabels(name) } template.Spec = podSpec.Get() return nil } // fillObjectMeta fills the metadata with the value calculated from config fillObjectMeta := func(meta *metav1.ObjectMeta) { meta.Annotations = annotations } // update supported controller for _, obj := range *objects { err := k.UpdateController(obj, fillTemplate, fillObjectMeta) if err != nil { return errors.Wrap(err, "k.UpdateController failed") } if len(service.Volumes) > 0 { switch objType := obj.(type) { case *appsv1.Deployment: objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType case *deployapi.DeploymentConfig: objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate } } } return nil } // UpdateKubernetesObjects loads configurations to k8s objects func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error { // Configure the environment variables. envs, envsFrom, err := ConfigEnvs(service, opt) if err != nil { return errors.Wrap(err, "Unable to load env variables") } // Configure the container volumes. volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service) if err != nil { return errors.Wrap(err, "k.ConfigVolumes failed") } // Configure Tmpfs if len(service.TmpFs) > 0 { TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service) volumes = append(volumes, TmpVolumes...) volumesMount = append(volumesMount, TmpVolumesMount...) } if pvc != nil && opt.Controller != StatefulStateController { // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)` // because the type of objects and pvc is different, but when doing append // one element at a time it gets converted to runtime.Object for objects slice for _, p := range pvc { *objects = append(*objects, p) } } for _, c := range cms { *objects = append(*objects, c) } // Configure the container ports. ports := ConfigPorts(service) // Configure capabilities capabilities := ConfigCapabilities(service) // Configure annotations annotations := transformer.ConfigAnnotations(service) // fillTemplate fills the pod template with the value calculated from config fillTemplate := func(template *api.PodTemplateSpec) error { template.Spec.Containers[0].Name = GetContainerName(service) template.Spec.Containers[0].Env = envs template.Spec.Containers[0].EnvFrom = envsFrom template.Spec.Containers[0].Command = service.Command template.Spec.Containers[0].Args = GetContainerArgs(service) template.Spec.Containers[0].WorkingDir = service.WorkingDir template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts, volumesMount...) template.Spec.Containers[0].Stdin = service.Stdin template.Spec.Containers[0].TTY = service.Tty if opt.Controller != StatefulStateController || opt.Volumes == "configMap" { template.Spec.Volumes = append(template.Spec.Volumes, volumes...) } template.Spec.Affinity = ConfigAffinity(service) template.Spec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service) // Configure the HealthCheck template.Spec.Containers[0].LivenessProbe = configProbe(service.HealthChecks.Liveness) template.Spec.Containers[0].ReadinessProbe = configProbe(service.HealthChecks.Readiness) if service.StopGracePeriod != "" { template.Spec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod) if err != nil { log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name) } } TranslatePodResource(&service, template) // Configure resource reservations podSecurityContext := &api.PodSecurityContext{} //set pid namespace mode if service.Pid != "" { if service.Pid == "host" { // podSecurityContext.HostPID = true } else { log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid) } } //set supplementalGroups if service.GroupAdd != nil { podSecurityContext.SupplementalGroups = service.GroupAdd } //set Security Context FsGroup if service.FsGroup != 0 { podSecurityContext.FSGroup = &service.FsGroup } // Setup security context securityContext := &api.SecurityContext{} if service.Privileged { securityContext.Privileged = &service.Privileged } if service.User != "" { switch userparts := strings.Split(service.User, ":"); len(userparts) { default: log.Warn("Ignoring ill-formed user directive. Must be in format UID or UID:GID.") case 1: uid, err := strconv.ParseInt(userparts[0], 10, 64) if err != nil { log.Warn("Ignoring user directive. User to be specified as a UID (numeric).") } else { securityContext.RunAsUser = &uid } case 2: uid, err := strconv.ParseInt(userparts[0], 10, 64) if err != nil { log.Warn("Ignoring user name in user directive. User to be specified as a UID (numeric).") } else { securityContext.RunAsUser = &uid } gid, err := strconv.ParseInt(userparts[1], 10, 64) if err != nil { log.Warn("Ignoring group name in user directive. Group to be specified as a GID (numeric).") } else { securityContext.RunAsGroup = &gid } } } //set capabilities if it is not empty if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 { securityContext.Capabilities = capabilities } //set readOnlyRootFilesystem if it is enabled if service.ReadOnly { securityContext.ReadOnlyRootFilesystem = &service.ReadOnly } // update template only if securityContext is not empty if *securityContext != (api.SecurityContext{}) { template.Spec.Containers[0].SecurityContext = securityContext } if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) { template.Spec.SecurityContext = podSecurityContext } template.Spec.Containers[0].Ports = ports // Only add network mode if generate-network-policies is set if opt.GenerateNetworkPolicies { template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network) } else { template.ObjectMeta.Labels = transformer.ConfigLabels(name) } // Configure the image pull policy policy, err := GetImagePullPolicy(name, service.ImagePullPolicy) if err != nil { return err } template.Spec.Containers[0].ImagePullPolicy = policy // Configure the container restart policy. restart, err := GetRestartPolicy(name, service.Restart) if err != nil { return err } template.Spec.RestartPolicy = restart // Configure hostname/domain_name settings if service.HostName != "" { template.Spec.Hostname = service.HostName } if service.DomainName != "" { template.Spec.Subdomain = service.DomainName } if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok { template.Spec.ServiceAccountName = serviceAccountName } fillInitContainers(template, service) return nil } // fillObjectMeta fills the metadata with the value calculated from config fillObjectMeta := func(meta *metav1.ObjectMeta) { meta.Annotations = annotations } // update supported controller for _, obj := range *objects { err = k.UpdateController(obj, fillTemplate, fillObjectMeta) if err != nil { return errors.Wrap(err, "k.UpdateController failed") } if len(service.Volumes) > 0 { switch objType := obj.(type) { case *appsv1.Deployment: objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType case *deployapi.DeploymentConfig: objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate case *appsv1.StatefulSet: // embed all PVCs inside the StatefulSet object if opt.Volumes == "configMap" { break } persistentVolumeClaims := make([]api.PersistentVolumeClaim, len(pvc)) for i, persistentVolumeClaim := range pvc { persistentVolumeClaims[i] = *persistentVolumeClaim persistentVolumeClaims[i].APIVersion = "" persistentVolumeClaims[i].Kind = "" } objType.Spec.VolumeClaimTemplates = persistentVolumeClaims } } } return nil } // getServiceVolumesID create a unique id for the service's volume mounts func getServiceVolumesID(service kobject.ServiceConfig) string { id := "" for _, v := range service.VolList { id += v } return id } // getServiceGroupID ... // return empty string should mean this service should go alone func getServiceGroupID(service kobject.ServiceConfig, mode string) string { if mode == "label" { return service.Labels[compose.LabelServiceGroup] } if mode == "volume" { return getServiceVolumesID(service) } return "" } // KomposeObjectToServiceConfigGroupMapping returns the service config group by name or by volume // This group function works as following // 1. Support two mode // (1): label: use a custom label, the service that contains it will be merged to one workload. // (2): volume: the service that share to exactly same volume config will be merged to one workload. If use pvc, only // create one for this group. // 2. If service containers restart policy and no workload argument provide and it's restart policy looks like a pod, then // this service should generate a pod. If group mode specified, it should be grouped and ignore the restart policy. // 3. If group mode specified, port conflict between services in one group will be ignored, and multiple service should be created. // 4. If `volume` group mode specified, we don't have an appropriate name for this combined service, use the first one for now. // A warn/info message should be printed to let the user know. func KomposeObjectToServiceConfigGroupMapping(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) map[string]kobject.ServiceConfigGroup { serviceConfigGroup := make(map[string]kobject.ServiceConfigGroup) sortedServiceConfigs := SortedKeys(komposeObject.ServiceConfigs) for _, service := range sortedServiceConfigs { serviceConfig := komposeObject.ServiceConfigs[service] groupID := getServiceGroupID(serviceConfig, opt.ServiceGroupMode) if groupID != "" { serviceConfig.Name = service serviceConfig.InGroup = true serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], serviceConfig) komposeObject.ServiceConfigs[service] = serviceConfig } } return serviceConfigGroup } // TranslatePodResource config pod resources func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) { // Configure the resource limits if service.MemLimit != 0 || service.CPULimit != 0 || service.DeployLabels["kompose.ephemeral-storage.limit"] != "" { resourceLimit := api.ResourceList{} if service.MemLimit != 0 { resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat") } if service.CPULimit != 0 { resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI) } // Check for ephemeral-storage in deploy labels if val, ok := service.DeployLabels["kompose.ephemeral-storage.limit"]; ok { if quantity, err := resource.ParseQuantity(val); err == nil { resourceLimit[api.ResourceEphemeralStorage] = quantity } } template.Spec.Containers[0].Resources.Limits = resourceLimit } // Configure the resource requests if service.MemReservation != 0 || service.CPUReservation != 0 || service.DeployLabels["kompose.ephemeral-storage.request"] != "" { resourceRequests := api.ResourceList{} if service.MemReservation != 0 { resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat") } if service.CPUReservation != 0 { resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI) } // Check for ephemeral-storage in deploy labels if val, ok := service.DeployLabels["kompose.ephemeral-storage.request"]; ok { if quantity, err := resource.ParseQuantity(val); err == nil { resourceRequests[api.ResourceEphemeralStorage] = quantity } } template.Spec.Containers[0].Resources.Requests = resourceRequests } } // GetImagePullPolicy get image pull settings func GetImagePullPolicy(name, policy string) (api.PullPolicy, error) { switch policy { case "": case "Always": return api.PullAlways, nil case "Never": return api.PullNever, nil case "IfNotPresent": return api.PullIfNotPresent, nil default: return "", errors.New("Unknown image-pull-policy " + policy + " for service " + name) } return "", nil } // GetRestartPolicy ... func GetRestartPolicy(name, restart string) (api.RestartPolicy, error) { switch restart { case "", "always", "any": return api.RestartPolicyAlways, nil case "no", "none": return api.RestartPolicyNever, nil case "on-failure": return api.RestartPolicyOnFailure, nil default: return "", errors.New("Unknown restart policy " + restart + " for service " + name) } } // SortServicesFirst - the objects that we get can be in any order this keeps services first // according to best practice kubernetes services should be created first // http://kubernetes.io/docs/user-guide/config-best-practices/ func (k *Kubernetes) SortServicesFirst(objs *[]runtime.Object) { var svc, others, ret []runtime.Object for _, obj := range *objs { if obj.GetObjectKind().GroupVersionKind().Kind == "Service" { svc = append(svc, obj) } else { others = append(others, obj) } } ret = append(ret, svc...) ret = append(ret, others...) *objs = ret } // RemoveDupObjects remove objects that are dups...eg. configmaps from env. // since we know for sure that the duplication can only happen on ConfigMap, so // this code will looks like this for now. // + NetworkPolicy func (k *Kubernetes) RemoveDupObjects(objs *[]runtime.Object) { var result []runtime.Object exist := map[string]bool{} for _, obj := range *objs { if us, ok := obj.(metav1.Object); ok { k := obj.GetObjectKind().GroupVersionKind().String() + us.GetNamespace() + us.GetName() if exist[k] { log.Debugf("Remove duplicate resource: %s/%s", obj.GetObjectKind().GroupVersionKind().Kind, us.GetName()) continue } else { result = append(result, obj) exist[k] = true } } else { result = append(result, obj) } } *objs = result } // SortedKeys Ensure the kubernetes objects are in a consistent order func SortedKeys[V kobject.ServiceConfig | kobject.ServiceConfigGroup](serviceConfig map[string]V) []string { var sortedKeys []string for name := range serviceConfig { sortedKeys = append(sortedKeys, name) } sort.Strings(sortedKeys) return sortedKeys } // DurationStrToSecondsInt converts duration string to *int64 in seconds func DurationStrToSecondsInt(s string) (*int64, error) { if s == "" { return nil, nil } duration, err := time.ParseDuration(s) if err != nil { return nil, err } r := (int64)(duration.Seconds()) return &r, nil } // GetEnvsFromFile get env vars from env_file func GetEnvsFromFile(file string) (map[string]string, error) { envLoad, err := godotenv.Read(file) if err != nil { return nil, errors.Wrap(err, "Unable to read env_file") } return envLoad, nil } func LoadEnvFiles(file string, lookup func(key string) (string, bool)) (map[string]string, error) { return dotenv.ReadWithLookup(lookup, file) } // GetContentFromFile gets the content from the file.. func GetContentFromFile(file string) (string, error) { fileBytes, err := os.ReadFile(file) if err != nil { return "", errors.Wrap(err, "Unable to read file") } return string(fileBytes), nil } // FormatEnvName format env name func FormatEnvName(name string, serviceName string) string { envName := strings.Trim(name, "./") // replace all non-alphanumerical characters with dashes to have a unique envName (env filename could be used multiple times) envName = regexp.MustCompile(`[^a-zA-Z0-9]`).ReplaceAllString(envName, "-") envName = getUsableNameEnvFile(envName, serviceName) return envName } // getUsableNameEnvFile checks and adjusts the environment file name to make it usable. // If the first character of envName is a hyphen "-", it is concatenated with nameService. // If the length of envName is greater than 63, it is truncated to 63 characters. // Returns the adjusted environment file name. func getUsableNameEnvFile(envName string, serviceName string) string { if string(envName[0]) == "-" { // -env-local.... envName = fmt.Sprintf("%s%s", serviceName, envName) } if len(envName) > 63 { envName = envName[0:63] } return envName } // FormatFileName format file name func FormatFileName(name string) string { // Split the filepath name so that we use the // file name (after the base) for ConfigMap, // it shouldn't matter whether it has special characters or not _, file := path.Split(name) // Make it DNS-1123 compliant for Kubernetes return strings.Replace(file, "_", "-", -1) } // FormatContainerName format Container name func FormatContainerName(name string) string { name = strings.Replace(name, "_", "-", -1) return name } // GetContainerName returns the name of the container, from the service config object func GetContainerName(service kobject.ServiceConfig) string { name := service.Name if len(service.ContainerName) > 0 { name = service.ContainerName } return FormatContainerName(name) } // FormatResourceName generate a valid k8s resource name func FormatResourceName(name string) string { return strings.ToLower(strings.Replace(name, "_", "-", -1)) } // GetContainerArgs update the interpolation of env variables if exists. // example: [curl, $PROTOCOL://$DOMAIN] => [curl, $(PROTOCOL)://$(DOMAIN)] func GetContainerArgs(service kobject.ServiceConfig) []string { var args []string re := regexp.MustCompile(`\$([a-zA-Z0-9]*)`) for _, arg := range service.Args { arg = re.ReplaceAllString(arg, `$($1)`) args = append(args, arg) } return args } // GetFileName extracts the file name from a given file path or file name. // If the input fileName contains a "/", it retrieves the substring after the last "/". // The function does not format the file name further, as it may contain periods or other valid characters. // Returns the extracted file name. func GetFileName(fileName string) string { return filepath.Base(fileName) } // reformatSecretConfigUnderscoreWithDash takes a ServiceSecretConfig object as input and returns a new instance of ServiceSecretConfig // where the values of Source and Target are formatted using the FormatResourceName function to replace underscores with dashes and lowercase, // while the other fields remain unchanged. This is done to ensure consistency in the format of container names within the service's secret configuration. // this function ensures that source, target names are in an acceptable format for Kubernetes and other systems that may require a specific naming format. func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConfig) types.ServiceSecretConfig { newSecretConfig := types.ServiceSecretConfig{ Source: FormatResourceName(secretConfig.Source), Target: FormatResourceName(secretConfig.Target), UID: secretConfig.UID, GID: secretConfig.GID, Mode: secretConfig.Mode, Extensions: secretConfig.Extensions, } return newSecretConfig } // fillInitContainers looks for an initContainer resources and its passed as labels // if there is no image, it does not fill the initContainer // https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ func fillInitContainers(template *api.PodTemplateSpec, service kobject.ServiceConfig) { resourceImage, exist := service.Labels[compose.LabelInitContainerImage] if !exist || resourceImage == "" { return } resourceName, exist := service.Labels[compose.LabelInitContainerName] if !exist || resourceName == "" { resourceName = "init-service" } template.Spec.InitContainers = append(template.Spec.InitContainers, api.Container{ Name: resourceName, Command: parseContainerCommandsFromStr(service.Labels[compose.LabelInitContainerCommand]), Image: resourceImage, }) } // parseContainerCommandsFromStr parses a string containing comma-separated commands // returns a slice of strings or a single command // example: // [ "bundle", "exec", "thin", "-p", "3000" ] // // example: // [ "bundle exec thin -p 3000" ] func parseContainerCommandsFromStr(line string) []string { if line == "" { return []string{} } var commands []string if strings.Contains(line, ",") { line = strings.TrimSpace(strings.Trim(line, "[]")) commands = strings.Split(line, ",") // remove space "' for i := range commands { commands[i] = strings.TrimSpace(strings.Trim(commands[i], `"' `)) } } else { commands = append(commands, line) } return commands } // searchHPAValues is useful to check if labels // contains any labels related to Horizontal Pod Autoscaler func searchHPAValues(labels map[string]string) bool { for _, value := range LabelKeys { if _, ok := labels[value]; ok { return true } } return false } // createHPAResources creates a HorizontalPodAutoscaler (HPA) resource // It sets the number of replicas in the service to 0 because // the number of replicas will be managed by the HPA func createHPAResources(name string, service *kobject.ServiceConfig) hpa.HorizontalPodAutoscaler { valuesHpa := getResourceHpaValues(service) service.Replicas = 0 metrics := getHpaMetricSpec(valuesHpa) scalerSpecs := hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: name, }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: name, APIVersion: "apps/v1", }, MinReplicas: &valuesHpa.MinReplicas, MaxReplicas: valuesHpa.MaxReplicas, Metrics: metrics, }, } return scalerSpecs } // getResourceHpaValues retrieves the min/max replicas and CPU/memory utilization values // control if maxReplicas is less than minReplicas func getResourceHpaValues(service *kobject.ServiceConfig) HpaValues { minReplicas := getHpaValue(service, compose.LabelHpaMinReplicas, DefaultMinReplicas) maxReplicas := getHpaValue(service, compose.LabelHpaMaxReplicas, DefaultMaxReplicas) if maxReplicas < minReplicas { log.Warnf("maxReplicas %d is less than minReplicas %d. Using minReplicas value %d", maxReplicas, minReplicas, minReplicas) maxReplicas = minReplicas } cpuUtilization := validatePercentageMetric(service, compose.LabelHpaCPU, DefaultCPUUtilization) memoryUtilization := validatePercentageMetric(service, compose.LabelHpaMemory, DefaultMemoryUtilization) return HpaValues{ MinReplicas: minReplicas, MaxReplicas: maxReplicas, CPUtilization: cpuUtilization, MemoryUtilization: memoryUtilization, } } // validatePercentageMetric validates the CPU or memory metrics value // ensuring that it falls within the acceptable range [1, 100]. func validatePercentageMetric(service *kobject.ServiceConfig, metricLabel string, defaultValue int32) int32 { metricValue := getHpaValue(service, metricLabel, defaultValue) if metricValue > 100 || metricValue < 1 { log.Warnf("Metric value %d is not within the acceptable range [1, 100]. Using default value %d", metricValue, defaultValue) return defaultValue } return metricValue } // getHpaValue convert the label value to integer // If the label is not present or the conversion fails // it returns the provided default value func getHpaValue(service *kobject.ServiceConfig, label string, defaultValue int32) int32 { valueFromLabel, err := strconv.Atoi(service.Labels[label]) if err != nil || valueFromLabel < 0 { log.Warnf("Error converting label %s. Using default value %d", label, defaultValue) return defaultValue } return int32(valueFromLabel) } // getHpaMetricSpec returns a list of metric specs for the HPA resource // Target type is hardcoded to hpa.UtilizationMetricType // Each MetricSpec specifies the type metric CPU/memory and average utilization value // to trigger scaling func getHpaMetricSpec(hpaValues HpaValues) []hpa.MetricSpec { var metrics []hpa.MetricSpec if hpaValues.CPUtilization > 0 { metrics = append(metrics, hpa.MetricSpec{ Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: api.ResourceCPU, Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &hpaValues.CPUtilization, }, }, }) } if hpaValues.MemoryUtilization > 0 { metrics = append(metrics, hpa.MetricSpec{ Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: api.ResourceMemory, Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &hpaValues.MemoryUtilization, }, }, }) } return metrics } // isConfigFile checks if the given filePath should be used as a configMap // if dir is not empty, withindir are treated as cofigmaps // if it's configMap, mount readonly as default func isConfigFile(filePath string) (useConfigMap bool, readonly bool, skip bool) { if filePath == "" || strings.HasSuffix(filePath, ".sock") { skip = true return } fi, err := os.Stat(filePath) if err != nil { log.Warnf("File don't exist or failed to check if the directory is empty: %v", err) // dir/file not exist // here not assigned skip to true, // maybe dont want to skip return } if !fi.Mode().IsRegular() { // is dir isDirEmpty, err := checkIsEmptyDir(filePath) if err != nil { log.Warnf("Failed to check if the directory is empty: %v", err) skip = true return } if isDirEmpty { return } } return true, true, skip } // checkIsEmptyDir checks if filepath is empty func checkIsEmptyDir(filePath string) (bool, error) { files, err := os.ReadDir(filePath) if err != nil { return false, err } if len(files) == 0 { return true, err } for _, file := range files { if !file.IsDir() { return false, nil } _, err := checkIsEmptyDir(file.Name()) if err != nil { return false, err } } return true, nil } // setVolumeAccessMode sets the access mode for a volume based on the mode string // current types: // ReadOnly RO and ReadOnlyMany ROX can be mounted in read-only mode to many hosts // ReadWriteMany RWX can be mounted in read/write mode to many hosts // ReadWriteOncePod RWOP can be mounted in read/write mode to exactly 1 pod // ReadWriteOnce RWO can be mounted in read/write mode to exactly 1 host // https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes func setVolumeAccessMode(mode string, volumeAccesMode []api.PersistentVolumeAccessMode) []api.PersistentVolumeAccessMode { switch mode { case "ro", "rox": volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadOnlyMany} case "rwx": volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteMany} case "rwop": volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOncePod} case "rwo": volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} default: volumeAccesMode = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} } return volumeAccesMode } // fixNetworkModeToService is responsible for adjusting the network mode of services in docker compose (services:) // generate a mapping of deployments based on the network mode of each service // merging containers into the destination deployment, and removing transferred deployments func (k *Kubernetes) fixNetworkModeToService(objects *[]runtime.Object, services map[string]kobject.ServiceConfig) { deploymentMappings := searchNetworkModeToService(services) if len(deploymentMappings) == 0 { return } mergeContainersIntoDestinationDeployment(deploymentMappings, objects) removeDeploymentTransfered(deploymentMappings, objects) } // mergeContainersIntoDestinationDeployment takes a list of deployment mappings and a list of runtime objects // and merges containers from source deployment into the destination deployment func mergeContainersIntoDestinationDeployment(deploymentMappings []DeploymentMapping, objects *[]runtime.Object) { for _, currentDeploymentMap := range deploymentMappings { addContainersFromSourceToTargetDeployment(objects, currentDeploymentMap) } } // addContainersFromSourceToTargetDeployment adds containers from the source deployment // if current deployment name matches source deployment name func addContainersFromSourceToTargetDeployment(objects *[]runtime.Object, currentDeploymentMap DeploymentMapping) { for _, obj := range *objects { if deploy, ok := obj.(*appsv1.Deployment); ok { if deploy.ObjectMeta.Name == currentDeploymentMap.SourceDeploymentName { addContainersToTargetDeployment(objects, deploy.Spec.Template.Spec.Containers, currentDeploymentMap.TargetDeploymentName) } } } } // addContainersToTargetDeployment takes // - list of runtime objects // - list of containers to append // - deployment name to transfer // appends the containers to the target deployment if its name matches func addContainersToTargetDeployment(objects *[]runtime.Object, containersToAppend []api.Container, nameDeploymentToTransfer string) { for _, obj := range *objects { if deploy, ok := obj.(*appsv1.Deployment); ok { if deploy.ObjectMeta.Name == nameDeploymentToTransfer { deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, containersToAppend...) } } } } // searchNetworkModeToService iterates over services and checking their network mode service: // its separates over process of transferring containers, // it determines where each container should be removed from and where it should be added to func searchNetworkModeToService(services map[string]kobject.ServiceConfig) (deploymentMappings []DeploymentMapping) { deploymentMappings = []DeploymentMapping{} for _, service := range services { if !strings.Contains(service.NetworkMode, NetworkModeService) { continue } splitted := strings.Split(service.NetworkMode, ":") if len(splitted) < 2 { continue } deploymentMappings = append(deploymentMappings, DeploymentMapping{ SourceDeploymentName: service.Name, TargetDeploymentName: splitted[1], }) } return deploymentMappings } // removeDeploymentTransfered iterates over a list of DeploymentMapping and // removes each deployment that marked in deploymentMappings func removeDeploymentTransfered(deploymentMappings []DeploymentMapping, objects *[]runtime.Object) { for _, currentDeploymentMap := range deploymentMappings { removeTargetDeployment(objects, currentDeploymentMap.SourceDeploymentName) } } // removeTargetDeployment iterates over a list of runtime objects // and removes the target deployment from the list func removeTargetDeployment(objects *[]runtime.Object, targetDeploymentName string) { for i := len(*objects) - 1; i >= 0; i-- { if deploy, ok := (*objects)[i].(*appsv1.Deployment); ok { if deploy.ObjectMeta.Name == targetDeploymentName { *objects = removeFromSlice(*objects, (*objects)[i]) } } } } // removeFromSlice removes a specific object from a slice of runtime objects and returns the updated slice func removeFromSlice(objects []runtime.Object, objectToRemove runtime.Object) []runtime.Object { for i, currentObject := range objects { if reflect.DeepEqual(currentObject, objectToRemove) { return append(objects[:i], objects[i+1:]...) } } return objects } ================================================ FILE: pkg/transformer/kubernetes/k8sutils_test.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package kubernetes import ( "fmt" "os" "path/filepath" "reflect" "sort" "testing" "github.com/compose-spec/compose-go/v2/types" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader/compose" "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" hpa "k8s.io/api/autoscaling/v2beta2" api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) /* Test the creation of a service */ func TestCreateService(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // not supported Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, // not supported CapDrop: []string{"cap_drop"}, // not supported Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} _, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Test the creation of the service svc := k.CreateService("foo", service) if svc.Spec.Ports[0].Port != 123 { t.Errorf("Expected port 123 upon conversion, actual %d", svc.Spec.Ports[0].Port) } } /* Test the creation of a service with a memory limit and reservation */ func TestCreateServiceWithMemLimit(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // not supported Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, // not supported CapDrop: []string{"cap_drop"}, // not supported Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", MemLimit: 1337, MemReservation: 1338, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Retrieve the deployment object and test that it matches the mem value for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { memLimit, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64() if memLimit != 1337 { t.Errorf("Expected 1337 for memory limit check, got %v", memLimit) } memReservation, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().AsInt64() if memReservation != 1338 { t.Errorf("Expected 1338 for memory reservation check, got %v", memReservation) } } } } /* Test the creation of a service with a cpu limit and reservation */ func TestCreateServiceWithCPULimit(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // not supported Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, // not supported CapDrop: []string{"cap_drop"}, // not supported Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", CPULimit: 10, CPUReservation: 1, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Retrieve the deployment object and test that it matches the cpu value for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { cpuLimit := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().MilliValue() if cpuLimit != 10 { t.Errorf("Expected 10 for cpu limit check, got %v", cpuLimit) } cpuReservation := deploy.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().MilliValue() if cpuReservation != 1 { t.Errorf("Expected 1 for cpu reservation check, got %v", cpuReservation) } } } } /* Test the creation of a service with ephemeral storage limit */ func TestDeployLabelsEphemeralStorageLimit(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, CapAdd: []string{"cap_add"}, CapDrop: []string{"cap_drop"}, Expose: []string{"expose"}, Privileged: true, Restart: "always", DeployLabels: map[string]string{"kompose.ephemeral-storage.limit": "1Gi"}, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Retrieve the deployment object and test that it matches the ephemeral storage limit value for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { storageLimit := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.StorageEphemeral() expectedLimit := resource.MustParse("1Gi") if *storageLimit != expectedLimit { t.Errorf("Expected %v for ephemeral storage limit check, got %v", expectedLimit, storageLimit) } } } } /* Test the creation of a service with ephemeral storage request */ func TestDeployLabelsEphemeralStorageRequest(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, CapAdd: []string{"cap_add"}, CapDrop: []string{"cap_drop"}, Expose: []string{"expose"}, Privileged: true, Restart: "always", DeployLabels: map[string]string{"kompose.ephemeral-storage.request": "1Gi"}, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Retrieve the deployment object and test that it matches the ephemeral storage request value for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { storageRequest := deploy.Spec.Template.Spec.Containers[0].Resources.Requests.StorageEphemeral() expectedRequest := resource.MustParse("1Gi") if *storageRequest != expectedRequest { t.Errorf("Expected %v for ephemeral storage request check, got %v", expectedRequest, storageRequest) } } } } /* Test the creation of a service with a specified user. The expected result is that Kompose will set user in PodSpec */ func TestCreateServiceWithServiceUser(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // not supported Labels: nil, Annotations: map[string]string{"kompose.service.type": "nodeport"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, // not supported CapDrop: []string{"cap_drop"}, // not supported Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", User: "1234:5678", } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { uid := *deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser gid := *deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup if fmt.Sprintf("%d:%d", uid, gid) != service.User { t.Errorf("User and group in ServiceConfig is not matching user in PodSpec") } } } } func TestCreateServiceWithConfigLongSyntax(t *testing.T) { content := "setting: true" target := "/etc/config.yaml" // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, Configs: []types.ServiceConfigObjConfig{{Source: "configmap", Target: target}}, ConfigsMetaData: map[string]types.ConfigObjConfig{"configmap": {Content: content}}, } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{ "app": service, }, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { t.Log(obj) if configMap, ok := obj.(*api.ConfigMap); ok { fileContent := configMap.Data["config.yaml"] if fileContent != content { t.Errorf("Config map content not equal") } } if deployment, ok := obj.(*appsv1.Deployment); ok { spec := deployment.Spec.Template.Spec if spec.Containers[0].VolumeMounts[0].MountPath != target { t.Errorf("Config map mountPath not found") } } } } func TestCreateServiceWithConfigShortSyntax(t *testing.T) { content := "setting: true" source := "configmap" target := "/" + source // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, Configs: []types.ServiceConfigObjConfig{{Source: source}}, ConfigsMetaData: map[string]types.ConfigObjConfig{source: {Content: content}}, } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{ "app": service, }, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { t.Log(obj) if configMap, ok := obj.(*api.ConfigMap); ok { fileContent := configMap.Data[source] if fileContent != content { t.Errorf("Config map content not equal") } } if deployment, ok := obj.(*appsv1.Deployment); ok { spec := deployment.Spec.Template.Spec if spec.Containers[0].VolumeMounts[0].MountPath != target { t.Errorf("Config map mountPath not found") } } } } func TestTransformWithPid(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, Restart: "always", Pid: "host", } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} _, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } //for _, obj := range objects { // if deploy, ok := obj.(*appsv1.Deployment); ok { // hostPid := deploy.Spec.Template.Spec.SecurityContext.HostPID // if !hostPid { // t.Errorf("Pid in ServiceConfig is not matching HostPID in PodSpec") // } // } //} } func TestTransformWithInvalidPid(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, Restart: "always", Pid: "badvalue", } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} _, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } //for _, obj := range objects { // if deploy, ok := obj.(*appsv1.Deployment); ok { // if deploy.Spec.Template.Spec.SecurityContext != nil { // hostPid := deploy.Spec.Template.Spec.SecurityContext.HostPID // if hostPid { // t.Errorf("Pid in ServiceConfig is not matching HostPID in PodSpec") // } // } // } //} } func TestIsDir(t *testing.T) { tempPath := "/tmp/kompose_unit" tempDir := filepath.Join(tempPath, "i_am_dir") tempFile := filepath.Join(tempPath, "i_am_file") tempAbsentDirPath := filepath.Join(tempPath, "i_do_not_exist") // create directory err := os.MkdirAll(tempDir, 0744) if err != nil { t.Errorf("Unable to create directory: %v", err) } // create empty file f, err := os.Create(tempFile) if err != nil { t.Errorf("Unable to create empty file: %v", err) } f.Close() // Check output if directory exists output, err := isDir(tempDir) if err != nil { t.Error(errors.Wrap(err, "isDir failed")) } if !output { t.Errorf("directory %v exists but isDir() returned %v", tempDir, output) } // Check output if file is provided output, err = isDir(tempFile) if err != nil { t.Error(errors.Wrap(err, "isDir failed")) } if output { t.Errorf("%v is a file but isDir() returned %v", tempDir, output) } // Check output if path does not exist output, err = isDir(tempAbsentDirPath) if err != nil { t.Error(errors.Wrap(err, "isDir failed")) } if output { t.Errorf("Directory %v does not exist, but isDir() returned %v", tempAbsentDirPath, output) } // delete temporary directory err = os.RemoveAll(tempPath) if err != nil { t.Errorf("Error removing the temporary directory during cleanup: %v", err) } } // TestServiceWithHealthCheck this tests if Headless Service is created for services with HealthCheck. func TestServiceWithHealthCheck(t *testing.T) { testCases := map[string]struct { service kobject.ServiceConfig }{ "Exec": { service: kobject.ServiceConfig{ ContainerName: "name", Image: "image", ServiceType: "Headless", HealthChecks: kobject.HealthChecks{ Readiness: kobject.HealthCheck{ Test: []string{"arg1", "arg2"}, Timeout: 10, Interval: 5, Retries: 3, StartPeriod: 60, }, Liveness: kobject.HealthCheck{ Test: []string{"arg1", "arg2"}, Timeout: 11, Interval: 6, Retries: 4, StartPeriod: 61, }, }, }, }, "HTTPGet": { service: kobject.ServiceConfig{ ContainerName: "name", Image: "image", ServiceType: "Headless", HealthChecks: kobject.HealthChecks{ Readiness: kobject.HealthCheck{ HTTPPath: "/health", HTTPPort: 8080, Timeout: 10, Interval: 5, Retries: 3, StartPeriod: 60, }, Liveness: kobject.HealthCheck{ HTTPPath: "/ready", HTTPPort: 8080, Timeout: 11, Interval: 6, Retries: 4, StartPeriod: 61, }, }, }, }, "TCPSocket": { service: kobject.ServiceConfig{ ContainerName: "name", Image: "image", ServiceType: "Headless", HealthChecks: kobject.HealthChecks{ Readiness: kobject.HealthCheck{ TCPPort: 8080, Timeout: 10, Interval: 5, Retries: 3, StartPeriod: 60, }, Liveness: kobject.HealthCheck{ TCPPort: 8080, Timeout: 11, Interval: 6, Retries: 4, StartPeriod: 61, }, }, }, }, } for _, testCase := range testCases { k := Kubernetes{} komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": testCase.service}, } objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } if err := testutils.CheckForHealthCheckLivenessAndReadiness(objects); err != nil { t.Error(err) } } } // TestServiceWithoutPort this tests if Headless Service is created for services without Port. func TestServiceWithoutPort(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", ServiceType: "Headless", } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } if err := testutils.CheckForHeadless(objects); err != nil { t.Error(err) } } // Tests if deployment strategy is being set to Recreate when volumes are // present func TestRecreateStrategyWithVolumesPresent(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", VolList: []string{"/tmp/volume"}, Volumes: []kobject.Volumes{{SvcName: "app", MountPath: "/tmp/volume", PVCName: "app-claim0"}}, } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { if deployment, ok := obj.(*appsv1.Deployment); ok { if deployment.Spec.Strategy.Type != appsv1.RecreateDeploymentStrategyType { t.Errorf("Expected %v as Strategy Type, got %v", appsv1.RecreateDeploymentStrategyType, deployment.Spec.Strategy.Type) } } } } func TestSortedKeys(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", } service1 := kobject.ServiceConfig{ ContainerName: "name", Image: "image", } c := []string{"a", "b"} komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"b": service, "a": service1}, } a := SortedKeys(komposeObject.ServiceConfigs) if !reflect.DeepEqual(a, c) { t.Logf("Test Fail output should be %s", c) } } // test conversion from duration string to seconds *int64 func TestDurationStrToSecondsInt(t *testing.T) { testCases := map[string]struct { in string out *int64 }{ "5s": {in: "5s", out: &[]int64{5}[0]}, "1m30s": {in: "1m30s", out: &[]int64{90}[0]}, "empty": {in: "", out: nil}, "onlynumber": {in: "2", out: nil}, "illegal": {in: "abc", out: nil}, } for name, test := range testCases { result, _ := DurationStrToSecondsInt(test.in) if test.out == nil && result != nil { t.Errorf("Case '%v' for TestDurationStrToSecondsInt fail, Expected 'nil' , got '%v'", name, *result) } if test.out != nil && result == nil { t.Errorf("Case '%v' for TestDurationStrToSecondsInt fail, Expected '%v' , got 'nil'", name, *test.out) } if test.out != nil && result != nil && *test.out != *result { t.Errorf("Case '%v' for TestDurationStrToSecondsInt fail, Expected '%v' , got '%v'", name, *test.out, *result) } } } func TestServiceWithServiceAccount(t *testing.T) { assertServiceAccountName := "my-service" service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Port: []kobject.Ports{{HostPort: 55555}}, Labels: map[string]string{compose.LabelServiceAccountName: assertServiceAccountName}, } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { if deployment, ok := obj.(*appsv1.Deployment); ok { if deployment.Spec.Template.Spec.ServiceAccountName != assertServiceAccountName { t.Errorf("Expected %v returned, got %v", assertServiceAccountName, deployment.Spec.Template.Spec.ServiceAccountName) } } } } func TestCreateServiceWithSpecialName(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "front_end", Image: "nginx", } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } expectedContainerName := "front-end" for _, obj := range objects { if deploy, ok := obj.(*appsv1.Deployment); ok { containerName := deploy.Spec.Template.Spec.Containers[0].Name if containerName != "front-end" { t.Errorf("Error while transforming container name. Expected %s Got %s", expectedContainerName, containerName) } } } } func TestArgsInterpolation(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "PROTOCOL", Value: "https"}, {Name: "DOMAIN", Value: "google.com"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"curl"}, Args: []string{"$PROTOCOL://$DOMAIN/"}, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } expectedArgs := []string{"$(PROTOCOL)://$(DOMAIN)/"} for _, obj := range objects { if deployment, ok := obj.(*appsv1.Deployment); ok { args := deployment.Spec.Template.Spec.Containers[0].Args[0] if args != expectedArgs[0] { t.Errorf("Expected args %v upon conversion, actual %v", expectedArgs, args) } } } } func TestReadOnlyRootFS(t *testing.T) { // An example service service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", ReadOnly: true, } // An example object generated via k8s runtime.Objects() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } k := Kubernetes{} objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objects { if deployment, ok := obj.(*appsv1.Deployment); ok { readOnlyFS := deployment.Spec.Template.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem if *readOnlyFS != true { t.Errorf("Expected ReadOnlyRootFileSystem %v upon conversion, actual %v", true, readOnlyFS) } } } } func TestFormatEnvName(t *testing.T) { type args struct { name string serviceName string } tests := []struct { name string args args want string }{ { name: "check dot conversion", args: args{ name: "random.test", }, want: "random-test", }, { name: "check that path is shortened", args: args{ name: "random/test/v1", }, want: "random-test-v1", }, { name: "check that ./ is removed", args: args{ name: "./random", }, want: "random", }, { name: "check that everything after $ is removed", args: args{ name: "abcdefghijklnmopqrstuvxyzabcdefghijklmnopqrstuvwxyzabcdejghijkl$Hereisadditional", }, want: "abcdefghijklnmopqrstuvxyzabcdefghijklmnopqrstuvwxyzabcdejghijkl", }, { name: "check that not begins with -", args: args{ name: "src/app/.env", serviceName: "app", }, want: "src-app--env", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FormatEnvName(tt.args.name, tt.args.serviceName); got != tt.want { t.Errorf("FormatEnvName() = %v, want %v", got, tt.want) } }) } } // Test empty interfaces removal func TestRemoveEmptyInterfaces(t *testing.T) { type Obj = map[string]interface{} var testCases = []struct { input interface{} output interface{} }{ {Obj{"useless": Obj{}}, Obj{}}, {Obj{"usefull": Obj{"usefull": "usefull"}}, Obj{"usefull": Obj{"usefull": "usefull"}}}, {Obj{"usefull": Obj{"usefull": "usefull", "uselessdeep": Obj{}, "uselessnil": nil}}, Obj{"usefull": Obj{"usefull": "usefull"}}}, {Obj{"uselessdeep": Obj{"uselessdeep": Obj{}, "uselessnil": nil}}, Obj{}}, {Obj{"uselessempty": []interface{}{nil}}, Obj{}}, {"test", "test"}, } for _, tc := range testCases { t.Run(fmt.Sprintf("Test removeEmptyInterfaces(%s)", tc.input), func(t *testing.T) { result := removeEmptyInterfaces(tc.input) if !reflect.DeepEqual(result, tc.output) { t.Errorf("Expected %v, got %v", tc.output, result) } }) } } func Test_parseContainerCommandsFromStr(t *testing.T) { tests := []struct { name string line string want []string }{ { name: "line command without spaces in between", line: `[ "bundle", "exec", "thin", "-p", "3000" ]`, want: []string{ "bundle", "exec", "thin", "-p", "3000", }, }, { name: `line command spaces inside ""`, line: `[ " bundle ", " exec ", " thin ", " -p ", "3000" ]`, want: []string{ "bundle", "exec", "thin", "-p", "3000", }, }, { name: `more use cases for line command spaces inside ""`, line: `[ " bundle ", "exec ", " thin ", " -p ", "3000 " ]`, want: []string{ "bundle", "exec", "thin", "-p", "3000", }, }, { name: `line command without [] and ""`, line: `bundle exec thin -p 3000`, want: []string{ "bundle exec thin -p 3000", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseContainerCommandsFromStr(tt.line); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseContainerCommandsFromStr() = %v, want %v", got, tt.want) } }) } } func Test_fillInitContainers(t *testing.T) { type args struct { template *api.PodTemplateSpec service kobject.ServiceConfig } tests := []struct { name string args args want []corev1.Container }{ { name: "Testing init container are generated from labels with ,", args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "name", compose.LabelInitContainerImage: "image", compose.LabelInitContainerCommand: `[ "bundle", "exec", "thin", "-p", "3000" ]`, }, }, }, want: []corev1.Container{ { Name: "name", Image: "image", Command: []string{ "bundle", "exec", "thin", "-p", "3000", }, }, }, }, { name: "Testing init container are generated from labels without ,", args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "name", compose.LabelInitContainerImage: "image", compose.LabelInitContainerCommand: `bundle exec thin -p 3000`, }, }, }, want: []corev1.Container{ { Name: "name", Image: "image", Command: []string{ `bundle exec thin -p 3000`, }, }, }, }, { name: `Testing init container with long command with vars inside and ''`, args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "init-myservice", compose.LabelInitContainerImage: "busybox:1.28", compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, }, }, }, want: []corev1.Container{ { Name: "init-myservice", Image: "busybox:1.28", Command: []string{ "sh", "-c", `until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done`, }, }, }, }, { name: `without image`, args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "init-myservice", compose.LabelInitContainerImage: "", compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, }, }, }, want: nil, }, { name: `Testing init container without name`, args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "", compose.LabelInitContainerImage: "busybox:1.28", compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, }, }, }, want: []corev1.Container{ { Name: "init-service", Image: "busybox:1.28", Command: []string{ "sh", "-c", `until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done`, }, }, }, }, { name: `Testing init container without command`, args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "init-service", compose.LabelInitContainerImage: "busybox:1.28", compose.LabelInitContainerCommand: ``, }, }, }, want: []corev1.Container{ { Name: "init-service", Image: "busybox:1.28", Command: []string{}, }, }, }, { name: `Testing init container without command`, args: args{ template: &api.PodTemplateSpec{}, service: kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelInitContainerName: "init-service", compose.LabelInitContainerImage: "busybox:1.28", }, }, }, want: []corev1.Container{ { Name: "init-service", Image: "busybox:1.28", Command: []string{}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fillInitContainers(tt.args.template, tt.args.service) if !reflect.DeepEqual(tt.args.template.Spec.InitContainers, tt.want) { t.Errorf("Test_fillInitContainers Fail got %v, want %v", tt.args.template.Spec.InitContainers, tt.want) } }) } } func Test_getHpaValue(t *testing.T) { type args struct { service *kobject.ServiceConfig label string defaultValue int32 } tests := []struct { name string args args want int32 }{ // LabelHpaMinReplicas { name: "LabelHpaMinReplicas with 1 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMinReplicas, defaultValue: 1, }, want: 1, }, { name: "LabelHpaMinReplicas with 0 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "0", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMinReplicas, defaultValue: 1, }, want: 0, }, { name: "LabelHpaMinReplicas with error value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "cannot transform", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMinReplicas, defaultValue: 1, }, want: 1, }, // LabelHpaMaxReplicas { name: "LabelHpaMaxReplicas with 10 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMaxReplicas, defaultValue: 30, }, want: 10, }, { name: "LabelHpaMaxReplicas with 0 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "0", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMaxReplicas, defaultValue: DefaultMaxReplicas, }, want: 0, }, { name: "LabelHpaMaxReplicas with error value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "cannot transform", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMaxReplicas, defaultValue: DefaultMaxReplicas, }, want: DefaultMaxReplicas, }, // LabelHpaCPU { name: "LabelHpaCPU with 50 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaCPU, defaultValue: 30, }, want: 50, }, { name: "LabelHpaCPU with 0 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "0", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: 0, }, { name: "LabelHpaCPU with error value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "cannot transform", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: DefaultCPUUtilization, }, // LabelHpaMemory { name: "LabelHpaMemory with 70 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, label: compose.LabelHpaMemory, defaultValue: 30, }, want: 70, }, { name: "LabelHpaMemory with 0 value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "0", }, }, label: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: 0, }, { name: "LabelHpaMemory with error value", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "cannot transform", }, }, label: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: DefaultMemoryUtilization, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getHpaValue(tt.args.service, tt.args.label, tt.args.defaultValue); got != tt.want { t.Errorf("getHpaValue() = %v, want %v", got, tt.want) } }) } } func Test_getResourceHpaValues(t *testing.T) { type args struct { service *kobject.ServiceConfig } tests := []struct { name string args args want HpaValues }{ { name: "check default values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "3", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, }, want: HpaValues{ MinReplicas: 1, MaxReplicas: 3, CPUtilization: 50, MemoryUtilization: 70, }, }, { name: "check if max replicas are less than min replicas, and max replicas set to min replicas", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "5", compose.LabelHpaMaxReplicas: "3", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, }, want: HpaValues{ MinReplicas: 5, MaxReplicas: 5, // same as min replicas CPUtilization: 50, MemoryUtilization: 70, }, }, { name: "with error values and use default values from LabelHpaMinReplicas", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "cannot transform", compose.LabelHpaMaxReplicas: "3", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: 3, CPUtilization: 50, MemoryUtilization: 70, }, }, { name: "LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "6", compose.LabelHpaMaxReplicas: "5", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, }, want: HpaValues{ MinReplicas: 6, MaxReplicas: 6, // set min replicas number CPUtilization: 50, MemoryUtilization: 70, }, }, { name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "6", compose.LabelHpaMaxReplicas: "5", compose.LabelHpaCPU: "cannot transform", compose.LabelHpaMemory: "70", }, }, }, want: HpaValues{ MinReplicas: 6, MaxReplicas: 6, // same as min replicas number CPUtilization: DefaultCPUUtilization, MemoryUtilization: 70, }, }, { name: "error label and LabelHpaMaxReplicas is minor to LabelHpaMinReplicas and cannot transform hpa mmemor utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "6", compose.LabelHpaMaxReplicas: "5", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "cannot transform", }, }, }, want: HpaValues{ MinReplicas: 6, MaxReplicas: 6, CPUtilization: 50, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "all error label, set all default values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "cannot transform", compose.LabelHpaMaxReplicas: "cannot transform", compose.LabelHpaCPU: "cannot transform", compose.LabelHpaMemory: "cannot transform", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "error label without some labels, missing labels set to default", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "cannot transform", compose.LabelHpaMaxReplicas: "cannot transform", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "without labels, should return default values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{}, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "only min replicas label is provided", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "3", }, }, }, want: HpaValues{ MinReplicas: 3, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "only max replicas label is provided", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMaxReplicas: "5", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: 5, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "check default values when all labels contain invalid values", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "cannot transform", compose.LabelHpaMaxReplicas: "cannot transform", compose.LabelHpaCPU: "cannot transform", compose.LabelHpaMemory: "cannot transform", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "only cpu utilization label is provided", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "80", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: 80, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "only memory utilization label is provided", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "90", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: 90, }, }, { name: "only cpu and memory utilization labels are provided", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "80", compose.LabelHpaMemory: "90", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: 80, MemoryUtilization: 90, }, }, { name: "check default values when labels are empty strings", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "", compose.LabelHpaMaxReplicas: "", compose.LabelHpaCPU: "", compose.LabelHpaMemory: "", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "check default values when labels contain invalid characters", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "abc", compose.LabelHpaMaxReplicas: "xyz", compose.LabelHpaCPU: "-100", compose.LabelHpaMemory: "invalid", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "check default values when labels are set to zero", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "0", compose.LabelHpaMaxReplicas: "0", compose.LabelHpaCPU: "0", compose.LabelHpaMemory: "0", }, }, }, want: HpaValues{ MinReplicas: 0, MaxReplicas: 0, CPUtilization: 50, MemoryUtilization: 70, }, }, { name: "check default values when all labels are negative", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "-5", compose.LabelHpaMaxReplicas: "-10", compose.LabelHpaCPU: "-20", compose.LabelHpaMemory: "-30", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, { name: "check default values when labels cpu and memory are over", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "-2", compose.LabelHpaMaxReplicas: "-2", compose.LabelHpaCPU: "120", compose.LabelHpaMemory: "120", }, }, }, want: HpaValues{ MinReplicas: DefaultMinReplicas, MaxReplicas: DefaultMaxReplicas, CPUtilization: DefaultCPUUtilization, MemoryUtilization: DefaultMemoryUtilization, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getResourceHpaValues(tt.args.service); !reflect.DeepEqual(got, tt.want) { t.Errorf("getResourceHpaValues() = %v, want %v", got, tt.want) } }) } } func Test_validatePercentageMetric(t *testing.T) { type args struct { service *kobject.ServiceConfig metricLabel string defaultValue int32 } tests := []struct { name string args args want int32 }{ { name: "0 cpu utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "0", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: 50, }, { name: "default cpu valid range", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "120", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: DefaultCPUUtilization, }, { name: "cpu invalid range", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "-120", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: DefaultCPUUtilization, }, { name: "cpu utilization set to 100", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "100", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: 100, }, { name: "cpu utlization set to 101", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "101", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: DefaultCPUUtilization, }, { name: "cannot convert value in cpu label", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaCPU: "not converted", }, }, metricLabel: compose.LabelHpaCPU, defaultValue: DefaultCPUUtilization, }, want: DefaultCPUUtilization, }, { name: "0 memory utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "0", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: 70, }, { name: "memory over 100 utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "120", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: DefaultMemoryUtilization, }, { name: "-120 utilization memory wrong range", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "-120", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: DefaultMemoryUtilization, }, { name: "memory 100 usage", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "100", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: 100, }, { name: "101 memory utilization", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "101", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: DefaultMemoryUtilization, }, { name: "cannot convert memory from label", args: args{ service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMemory: "not converted", }, }, metricLabel: compose.LabelHpaMemory, defaultValue: DefaultMemoryUtilization, }, want: DefaultMemoryUtilization, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := validatePercentageMetric(tt.args.service, tt.args.metricLabel, tt.args.defaultValue); got != tt.want { t.Errorf("validatePercentageMetric() = %v, want %v", got, tt.want) } }) } } func Test_getHpaMetricSpec(t *testing.T) { valueCPUFixed := int32(50) valueMemoryFixed := int32(70) valueOver100 := int32(120) valueUnderZero := int32(-120) // valueZero := int32(0) type args struct { hpaValues HpaValues } tests := []struct { name string args args want []hpa.MetricSpec }{ { name: "no values", args: args{ hpaValues: HpaValues{}, // set all values to 0 }, want: nil, }, { name: "only cpu", args: args{ hpaValues: HpaValues{ CPUtilization: valueCPUFixed, }, }, want: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, }, }, { name: "only memory", args: args{ hpaValues: HpaValues{ MemoryUtilization: 70, }, }, want: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, { name: "cpu and memory", args: args{ hpaValues: HpaValues{ CPUtilization: valueCPUFixed, MemoryUtilization: valueMemoryFixed, }, }, want: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, { name: "memory over 100", args: args{ hpaValues: HpaValues{ MemoryUtilization: valueOver100, }, }, want: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueOver100, }, }, }, }, }, { name: "cpu and memory over 100", args: args{ hpaValues: HpaValues{ CPUtilization: valueOver100, MemoryUtilization: valueOver100, }, }, want: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueOver100, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueOver100, }, }, }, }, }, { name: "cpu and memory under 0", args: args{ hpaValues: HpaValues{ CPUtilization: valueUnderZero, MemoryUtilization: valueUnderZero, }, }, want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getHpaMetricSpec(tt.args.hpaValues); !reflect.DeepEqual(got, tt.want) { t.Errorf("getHpaMetricSpec() = %v, want %v", got, tt.want) } }) } } func Test_createHPAResources(t *testing.T) { valueCPUFixed := int32(50) valueMemoryFixed := int32(70) fixedMinReplicas := int32(1) type args struct { name string service *kobject.ServiceConfig } tests := []struct { name string args args want hpa.HorizontalPodAutoscaler }{ { name: "all labels", args: args{ name: "web", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "10", compose.LabelHpaCPU: "50", compose.LabelHpaMemory: "70", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "web", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "web", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: 10, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, { name: "minimum labels", args: args{ name: "api", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaCPU: "50", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "api", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "api", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: DefaultMaxReplicas, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, { name: "missing CPU utilization label", args: args{ name: "app", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "5", compose.LabelHpaMemory: "70", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "app", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "app", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: 5, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, { name: "missing memory utilization label", args: args{ name: "db", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "8", compose.LabelHpaCPU: "50", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "db", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "db", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: 8, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, { name: "wrong labels", args: args{ name: "db", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "not converted", compose.LabelHpaMaxReplicas: "not converted", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "db", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "db", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: DefaultMaxReplicas, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, { name: "missing both CPU and memory utilization labels", args: args{ name: "db", service: &kobject.ServiceConfig{ Labels: map[string]string{ compose.LabelHpaMinReplicas: "1", compose.LabelHpaMaxReplicas: "5", }, }, }, want: hpa.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v2", }, ObjectMeta: metav1.ObjectMeta{ Name: "db", }, Spec: hpa.HorizontalPodAutoscalerSpec{ ScaleTargetRef: hpa.CrossVersionObjectReference{ Kind: "Deployment", Name: "db", APIVersion: "apps/v1", }, MinReplicas: &fixedMinReplicas, MaxReplicas: 5, Metrics: []hpa.MetricSpec{ { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "cpu", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueCPUFixed, }, }, }, { Type: hpa.ResourceMetricSourceType, Resource: &hpa.ResourceMetricSource{ Name: "memory", Target: hpa.MetricTarget{ Type: hpa.UtilizationMetricType, AverageUtilization: &valueMemoryFixed, }, }, }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := createHPAResources(tt.args.name, tt.args.service); !reflect.DeepEqual(got, tt.want) { t.Errorf("createHPAResources() = %v, want %v", got, tt.want) } }) } } func Test_setVolumeAccessMode(t *testing.T) { type args struct { mode string volumeAccesMode []api.PersistentVolumeAccessMode } tests := []struct { name string args args want []api.PersistentVolumeAccessMode }{ { name: "readonly", args: args{ mode: "ro", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, }, { name: "not acceptable", args: args{ mode: "wrong", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, }, { name: "readonly many", args: args{ mode: "rox", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, }, { name: "readwrite many", args: args{ mode: "rwx", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, }, { name: "readwrite once in pod", args: args{ mode: "rwop", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadWriteOncePod}, }, { name: "readwrite once", args: args{ mode: "rwo", volumeAccesMode: []api.PersistentVolumeAccessMode{}, }, want: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := setVolumeAccessMode(tt.args.mode, tt.args.volumeAccesMode); !reflect.DeepEqual(got, tt.want) { t.Errorf("setVolumeAccessMode() = %v, want %v", got, tt.want) } }) } } func Test_isConfigFile(t *testing.T) { type args struct { filePath string } tests := []struct { name string args args wantUseConfigMap bool wantReadonly bool wantSkip bool }{ { name: "dir not empty", args: args{ filePath: "../../../script/test/fixtures/configmap-file-configs/certs", }, wantUseConfigMap: true, wantReadonly: true, wantSkip: false, }, { name: "sock", args: args{ filePath: "./docker.sock", }, wantUseConfigMap: false, wantReadonly: false, wantSkip: true, }, { name: "cannot resolve filepath", args: args{ filePath: "./certs/cert1.pem", }, wantUseConfigMap: false, wantReadonly: false, wantSkip: false, }, { name: "file cert", args: args{ filePath: "../../../script/test/fixtures/configmap-file-configs/certs/cert1.pem", }, wantUseConfigMap: true, wantReadonly: true, wantSkip: false, }, { name: "docker sock", args: args{ filePath: "/var/run/docker.sock", }, wantUseConfigMap: false, wantReadonly: false, wantSkip: true, }, { name: "file from 3 levels", args: args{ filePath: "../../../script/test/fixtures/configmap-file-configs/certs-level1/certs-level2/certs-level3/cert2.pem", }, wantUseConfigMap: true, wantReadonly: true, wantSkip: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotUseConfigMap, gotReadonly, gotSkip := isConfigFile(tt.args.filePath) if gotUseConfigMap != tt.wantUseConfigMap { t.Errorf("isConfigFile() gotUseConfigMap = %v, want %v", gotUseConfigMap, tt.wantUseConfigMap) } if gotReadonly != tt.wantReadonly { t.Errorf("isConfigFile() gotReadonly = %v, want %v", gotReadonly, tt.wantReadonly) } if gotSkip != tt.wantSkip { t.Errorf("isConfigFile() gotSkip = %v, want %v", gotSkip, tt.wantSkip) } }) } } func Test_checkIsEmptyDir(t *testing.T) { type args struct { filePath string } tests := []struct { name string args args want bool wantErr bool }{ { name: "dir not found", args: args{ filePath: "../../../script/test/fixtures/configmap-file-configs/notfound", }, want: false, wantErr: true, }, { name: "dir not empty", args: args{ filePath: "../../../script/test/fixtures/configmap-file-configs/certs", }, want: false, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := checkIsEmptyDir(tt.args.filePath) if (err != nil) != tt.wantErr { t.Errorf("checkIsEmptyDir() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("checkIsEmptyDir() = %v, want %v", got, tt.want) } }) } } func Test_removeFromSlice(t *testing.T) { type args struct { objects []runtime.Object objectToRemove runtime.Object } tests := []struct { name string args args want []runtime.Object }{ { name: "remove object in the middle", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove objects in the middle and last object", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, }, { name: "remove 1 object", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove object at the beginning", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove object at the end", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove object that doesn't exist", args: args{ objects: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "missing"}}, }, want: []runtime.Object{ &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove object from empty slice", args: args{ objects: []runtime.Object{}, objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, want: []runtime.Object{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := removeFromSlice(tt.args.objects, tt.args.objectToRemove); !reflect.DeepEqual(got, tt.want) { t.Errorf("removeFromSlice() = %v, want %v", got, tt.want) } }) } } func Test_removeTargetDeployment(t *testing.T) { type args struct { objects *[]runtime.Object targetDeploymentName string } tests := []struct { name string args args want *[]runtime.Object }{ { name: "remove middle object", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, targetDeploymentName: "remove", }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove 2 objects from slice", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, targetDeploymentName: "remove", }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove 2 object from slice, only persist last one", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, targetDeploymentName: "remove", }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove target deployment from an empty slice", args: args{ objects: &[]runtime.Object{}, targetDeploymentName: "remove", }, want: &[]runtime.Object{}, }, { name: "remove target deployment that does not exist", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, targetDeploymentName: "missing", }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, { name: "remove target deployment from a slice with only the target deployment", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, }, targetDeploymentName: "remove", }, want: &[]runtime.Object{}, }, { name: "remove all targets", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, targetDeploymentName: "remove", }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { removeTargetDeployment(tt.args.objects, tt.args.targetDeploymentName) if !reflect.DeepEqual(tt.args.objects, tt.want) { t.Errorf("removeFromSlice() = %v, want %v", tt.args.objects, tt.want) } }) } } func Test_removeDeploymentTransfered(t *testing.T) { type args struct { deploymentMappings []DeploymentMapping objects *[]runtime.Object } tests := []struct { name string args args want *[]runtime.Object }{ { name: "remove deployment already transferred", args: args{ deploymentMappings: []DeploymentMapping{ { TargetDeploymentName: "app", SourceDeploymentName: "db", }, }, objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, }, }, { name: "remove multiple deployments already transferred", args: args{ deploymentMappings: []DeploymentMapping{ { TargetDeploymentName: "app", SourceDeploymentName: "db", }, { TargetDeploymentName: "web", SourceDeploymentName: "cache", }, }, objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "web"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "cache"}}, }, }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "web"}}, }, }, { name: "remove deployment transferred with multiple containers", args: args{ deploymentMappings: []DeploymentMapping{ { TargetDeploymentName: "app", SourceDeploymentName: "db", }, }, objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, }, }, { name: "remove deployment transferred with different container names", args: args{ deploymentMappings: []DeploymentMapping{ { TargetDeploymentName: "app", SourceDeploymentName: "db", }, }, objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, }, }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, }, }, { name: "remove deployment transferred when no matching source deployment", args: args{ deploymentMappings: []DeploymentMapping{ { TargetDeploymentName: "app", SourceDeploymentName: "db", }, }, objects: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, }, }, want: &[]runtime.Object{ &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { removeDeploymentTransfered(tt.args.deploymentMappings, tt.args.objects) if !reflect.DeepEqual(tt.args.objects, tt.want) { t.Errorf("removeFromSlice() = %v, want %v", tt.args.objects, tt.want) } }) } } func Test_searchNetworkModeToService(t *testing.T) { tests := []struct { name string services map[string]kobject.ServiceConfig want []DeploymentMapping }{ { name: "search network mode to service", services: map[string]kobject.ServiceConfig{ "app": { Name: "app", }, "db": { Name: "db", NetworkMode: "service:app", }, }, want: []DeploymentMapping{ { SourceDeploymentName: "db", TargetDeploymentName: "app", }, }, }, { name: "error and not set service:app", services: map[string]kobject.ServiceConfig{ "app": { Name: "app", }, "db": { Name: "db", }, }, want: []DeploymentMapping{}, }, { name: "search network mode to service with multiple source deployments", services: map[string]kobject.ServiceConfig{ "app": { Name: "app", }, "db1": { Name: "db1", NetworkMode: "service:app", }, "db2": { Name: "db2", NetworkMode: "service:app", }, }, want: []DeploymentMapping{ { SourceDeploymentName: "db1", TargetDeploymentName: "app", }, { SourceDeploymentName: "db2", TargetDeploymentName: "app", }, }, }, { name: "search network mode to service with multiple target deployments", services: map[string]kobject.ServiceConfig{ "app1": { Name: "app1", }, "app2": { Name: "app2", }, "db": { Name: "db", NetworkMode: "service:app1", }, }, want: []DeploymentMapping{ { SourceDeploymentName: "db", TargetDeploymentName: "app1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotDeploymentMappings := searchNetworkModeToService(tt.services) sort.Slice(gotDeploymentMappings, func(i, j int) bool { if gotDeploymentMappings[i].SourceDeploymentName != gotDeploymentMappings[j].SourceDeploymentName { return gotDeploymentMappings[i].SourceDeploymentName < gotDeploymentMappings[j].SourceDeploymentName } return gotDeploymentMappings[i].TargetDeploymentName < gotDeploymentMappings[j].TargetDeploymentName }) sort.Slice(tt.want, func(i, j int) bool { if tt.want[i].SourceDeploymentName != tt.want[j].SourceDeploymentName { return tt.want[i].SourceDeploymentName < tt.want[j].SourceDeploymentName } return tt.want[i].TargetDeploymentName < tt.want[j].TargetDeploymentName }) if !reflect.DeepEqual(gotDeploymentMappings, tt.want) { t.Errorf("searchNetworkModeToService() = %v, want %v", gotDeploymentMappings, tt.want) } }) } } func Test_addContainersToTargetDeployment(t *testing.T) { k := Kubernetes{} appK, err := k.Transform( kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": { ContainerName: "app", Image: "image", }, }, }, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } dbK, err := k.Transform( kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"db": { ContainerName: "db", Image: "image", NetworkMode: "service:app", }, }, }, kobject.ConvertOptions{CreateD: true, Replicas: 3}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } type args struct { objects []runtime.Object containersToAppend []api.Container nameDeploymentToTransfer string } const ( FirstObject = 0 SecondObject = 1 ) tests := []struct { name string args args wantAfter int wantBefore int targetObject int }{ { name: "no containers to add, appk target transfer", args: args{ objects: []runtime.Object{appK[0]}, containersToAppend: []api.Container{}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 1, targetObject: FirstObject, }, { name: "no match in the deployment names, no target found", args: args{ objects: []runtime.Object{dbK[0]}, containersToAppend: []api.Container{}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 1, targetObject: FirstObject, }, { name: "1 container more, appk target transfer", args: args{ objects: []runtime.Object{appK[0]}, containersToAppend: []api.Container{{Name: "new-1"}}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 2, targetObject: FirstObject, }, { name: "1 containers and 2 more, appk target transfer", args: args{ objects: []runtime.Object{appK[0], dbK[0]}, containersToAppend: []api.Container{{Name: "new-1"}, {Name: "new-2"}}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 3, targetObject: FirstObject, }, { name: "one containers to transfer, appk target transfer", args: args{ objects: []runtime.Object{appK[0]}, containersToAppend: []api.Container{{Name: "new-1"}}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 2, targetObject: FirstObject, }, { name: "1 container in appk and add 2 container in dbK, db target transfer", args: args{ objects: []runtime.Object{appK[0], dbK[0]}, containersToAppend: []api.Container{{Name: "new-1"}, {Name: "new-2"}}, nameDeploymentToTransfer: "db", }, wantBefore: 1, wantAfter: 3, targetObject: SecondObject, }, { name: "1 container in appk, cannot add 2 container in dbK, app target transfer", args: args{ objects: []runtime.Object{appK[0], dbK[0]}, containersToAppend: []api.Container{{Name: "new-1"}, {Name: "new-2"}}, nameDeploymentToTransfer: "app", }, wantBefore: 1, wantAfter: 1, targetObject: SecondObject, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { beforeContainers := (tt.args.objects)[tt.targetObject].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(beforeContainers) != tt.wantBefore { t.Errorf("Before Expected %d containers, got %d", tt.wantBefore, len(beforeContainers)) } addContainersToTargetDeployment(&tt.args.objects, tt.args.containersToAppend, tt.args.nameDeploymentToTransfer) afterContainers := (tt.args.objects)[tt.targetObject].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(afterContainers) != tt.wantAfter { t.Errorf("After Expected %d containers, got %d", tt.wantAfter, len(afterContainers)) } // reset containers newContainer := appK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers[0] appK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers = nil appK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers = append(appK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers, newContainer) newContainer = dbK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers[0] dbK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers = nil dbK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers = append(dbK[0].(*appsv1.Deployment).Spec.Template.Spec.Containers, newContainer) }) } } func Test_addContainersFromSourceToTargetDeployment(t *testing.T) { type args struct { objects *[]runtime.Object currentDeploymentMap DeploymentMapping } const ( FirstObject = 0 SecondObject = 1 ) tests := []struct { name string args args wantBefore int wantAfter int targetDeployment int }{ { name: "add one container more to target deployment", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, currentDeploymentMap: DeploymentMapping{ SourceDeploymentName: "db", TargetDeploymentName: "app", }, }, wantBefore: 1, wantAfter: 2, targetDeployment: FirstObject, }, { name: "no containers to transfer", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "new-1", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, currentDeploymentMap: DeploymentMapping{}, }, wantBefore: 1, wantAfter: 1, targetDeployment: FirstObject, }, { name: "no containers to transfer", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "new-1", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, currentDeploymentMap: DeploymentMapping{ SourceDeploymentName: "app", TargetDeploymentName: "db", }, }, wantBefore: 1, wantAfter: 2, targetDeployment: SecondObject, }, { name: "target deployment not found", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "new-1", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, currentDeploymentMap: DeploymentMapping{ SourceDeploymentName: "no-exist-deployment", TargetDeploymentName: "target-no-exist", }, }, wantBefore: 1, wantAfter: 1, targetDeployment: FirstObject, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { beforeContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(beforeContainers) != tt.wantBefore { t.Errorf("Before expected %d containers, got %d", tt.wantBefore, len(beforeContainers)) } addContainersFromSourceToTargetDeployment(tt.args.objects, tt.args.currentDeploymentMap) afterContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(afterContainers) != tt.wantAfter { t.Errorf("After expected %d containers, got %d", tt.wantAfter, len(afterContainers)) } }) } } func Test_mergeContainersIntoDestinationDeployment(t *testing.T) { type args struct { deploymentMappings []DeploymentMapping objects *[]runtime.Object } const ( FirstObject = 0 SecondObject = 1 ) tests := []struct { name string args args wantBefore int wantAfter int targetDeployment int }{ { name: "merge containers into db", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{}, }, }, }, }, }, deploymentMappings: []DeploymentMapping{ { SourceDeploymentName: "app", TargetDeploymentName: "db", }, }, }, wantBefore: 0, wantAfter: 1, targetDeployment: SecondObject, }, { name: "merge containers into destination deployment", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db-1", Image: "image", }, { Name: "db-2", Image: "image", }, { Name: "db-3", Image: "image", }, }, }, }, }, }, }, deploymentMappings: []DeploymentMapping{ { SourceDeploymentName: "db", TargetDeploymentName: "app", }, }, }, wantBefore: 1, wantAfter: 4, targetDeployment: FirstObject, }, { name: "no containers to transfer", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, deploymentMappings: []DeploymentMapping{}, }, wantBefore: 1, wantAfter: 1, targetDeployment: FirstObject, }, { name: "merge 2 containers db deployment into app deployment", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, { Name: "db-2", Image: "image", }, }, }, }, }, }, }, deploymentMappings: []DeploymentMapping{ { SourceDeploymentName: "db", TargetDeploymentName: "app", }, }, }, wantBefore: 1, wantAfter: 3, targetDeployment: FirstObject, }, { name: "merge containers in app deployment into db deployment", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, deploymentMappings: []DeploymentMapping{ { SourceDeploymentName: "app", TargetDeploymentName: "db", }, }, }, wantBefore: 1, wantAfter: 2, targetDeployment: SecondObject, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { beforeContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(beforeContainers) != tt.wantBefore { t.Errorf("Before expected %d containers, got %d", tt.wantBefore, len(beforeContainers)) } mergeContainersIntoDestinationDeployment(tt.args.deploymentMappings, tt.args.objects) afterContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(afterContainers) != tt.wantAfter { t.Errorf("After expected %d containers, got %d", tt.wantAfter, len(afterContainers)) } }) } } func TestKubernetes_fixNetworkModeToService(t *testing.T) { type args struct { objects *[]runtime.Object services map[string]kobject.ServiceConfig } const ( FirstObject = 0 SecondObject = 1 ) tests := []struct { name string args args wantBefore int wantAfter int targetDeployment int wantDeployments int }{ { name: "fix network mode to service with transfer", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "nginx", Image: "image", }, }, }, }, }, }, }, services: map[string]kobject.ServiceConfig{ "nginx": { Name: "nginx", Image: "image", }, "app": { Name: "app", Image: "image", NetworkMode: "service:db", }, "db": { Name: "db", Image: "image", }, }, }, wantBefore: 1, wantAfter: 2, targetDeployment: FirstObject, wantDeployments: 2, }, { name: "not transfer because wronge service name", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, services: map[string]kobject.ServiceConfig{ "app": { Name: "app", Image: "image", }, "db": { Name: "db", Image: "image", NetworkMode: "service:wrong", }, }, }, wantBefore: 1, wantAfter: 1, targetDeployment: FirstObject, wantDeployments: 1, }, { name: "deployment db removed", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, services: map[string]kobject.ServiceConfig{ "app": { Name: "app", Image: "image", NetworkMode: "service:db", }, "db": { Name: "db", Image: "image", }, }, }, wantBefore: 1, wantAfter: 2, targetDeployment: FirstObject, // db deployment removed wantDeployments: 1, }, { name: "deployment app removed and added 1 container", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db", Image: "image", }, }, }, }, }, }, }, services: map[string]kobject.ServiceConfig{ "app": { Name: "app", Image: "image", NetworkMode: "service:db", }, "db": { Name: "db", Image: "image", }, }, }, wantBefore: 1, wantAfter: 2, targetDeployment: FirstObject, wantDeployments: 1, }, { name: "deployment db removed and added 3 containers", args: args{ objects: &[]runtime.Object{ &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "app"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "app", Image: "image", }, }, }, }, }, }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "db"}, Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "db-1", Image: "image", }, { Name: "db-2", Image: "image", }, { Name: "db-3", Image: "image", }, }, }, }, }, }, }, services: map[string]kobject.ServiceConfig{ "app": { Name: "app", Image: "image", NetworkMode: "service:db", }, "db": { Name: "db", Image: "image", }, }, }, wantBefore: 1, wantAfter: 4, targetDeployment: FirstObject, // db deployment removed wantDeployments: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &Kubernetes{} beforeContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(beforeContainers) != tt.wantBefore { t.Errorf("Expected %d containers, got %d", tt.wantBefore, len(beforeContainers)) } k.fixNetworkModeToService(tt.args.objects, tt.args.services) afterContainers := (*tt.args.objects)[tt.targetDeployment].(*appsv1.Deployment).Spec.Template.Spec.Containers if len(afterContainers) != tt.wantAfter { t.Errorf("Expected %d containers, got %d", tt.wantAfter, len(afterContainers)) } if len(*tt.args.objects) != tt.wantDeployments { t.Errorf("Expected %d deployments, got %d", tt.wantDeployments, len(*tt.args.objects)) } }) } } ================================================ FILE: pkg/transformer/kubernetes/kubernetes.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package kubernetes import ( "encoding/base64" "fmt" "os" "os/exec" "path" "path/filepath" "reflect" "regexp" "sort" "strconv" "strings" "github.com/compose-spec/compose-go/v2/types" "github.com/fatih/structs" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader/compose" "github.com/kubernetes/kompose/pkg/transformer" "github.com/mattn/go-shellwords" deployapi "github.com/openshift/api/apps/v1" buildapi "github.com/openshift/api/build/v1" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cast" "golang.org/x/tools/godoc/util" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" api "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) // Kubernetes implements Transformer interface and represents Kubernetes transformer type Kubernetes struct { // the user provided options from the command line Opt kobject.ConvertOptions } // PVCRequestSize (Persistent Volume Claim) has default size const PVCRequestSize = "100Mi" // ValidVolumeSet has the different types of valid volumes var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}} const ( // DeploymentController is controller type for Deployment DeploymentController = "deployment" // DaemonSetController is controller type for DaemonSet DaemonSetController = "daemonset" // StatefulStateController is controller type for StatefulSet StatefulStateController = "statefulset" ) // CheckUnsupportedKey checks if given komposeObject contains // keys that are not supported by this transformer. // list of all unsupported keys are stored in unsupportedKey variable // returns list of TODO: .... func (k *Kubernetes) CheckUnsupportedKey(komposeObject *kobject.KomposeObject, unsupportedKey map[string]bool) []string { // collect all keys found in project var keysFound []string for _, serviceConfig := range komposeObject.ServiceConfigs { // this reflection is used in check for empty arrays val := reflect.ValueOf(serviceConfig) s := structs.New(serviceConfig) for _, f := range s.Fields() { // Check if given key is among unsupported keys, and skip it if we already saw this key if alreadySaw, ok := unsupportedKey[f.Name()]; ok && !alreadySaw { if f.IsExported() && !f.IsZero() { // IsZero returns false for empty array/slice ([]) // this check if field is Slice, and then it checks its size if field := val.FieldByName(f.Name()); field.Kind() == reflect.Slice { if field.Len() == 0 { // array is empty it doesn't matter if it is in unsupportedKey or not continue } } //get tag from kobject service configure tag := f.Tag(komposeObject.LoadedFrom) keysFound = append(keysFound, tag) unsupportedKey[f.Name()] = true } } } } return keysFound } // InitPodSpec creates the pod specification func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) api.PodSpec { if image == "" { image = name } pod := api.PodSpec{ Containers: []api.Container{ { Name: name, Image: image, }, }, } if pullSecret != "" { pod.ImagePullSecrets = []api.LocalObjectReference{ { Name: pullSecret, }, } } return pod } // InitPodSpecWithConfigMap creates the pod specification func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service kobject.ServiceConfig) api.PodSpec { var volumeMounts []api.VolumeMount var volumes []api.Volume for _, value := range service.Configs { cmVolName := FormatFileName(value.Source) target := value.Target if target == "" { // short syntax, = / target = "/" + value.Source } subPath := filepath.Base(target) volSource := api.ConfigMapVolumeSource{} volSource.Name = cmVolName key, err := service.GetConfigMapKeyFromMeta(value.Source) if err != nil { log.Warnf("cannot parse config %s , %s", value.Source, err.Error()) // mostly it's external continue } volSource.Items = []api.KeyToPath{{ Key: key, Path: subPath, }} if value.Mode != nil { tmpMode := int32(*value.Mode) volSource.DefaultMode = &tmpMode } cmVol := api.Volume{ Name: cmVolName, VolumeSource: api.VolumeSource{ConfigMap: &volSource}, } volumeMounts = append(volumeMounts, api.VolumeMount{ Name: cmVolName, MountPath: target, SubPath: subPath, }) volumes = append(volumes, cmVol) } pod := api.PodSpec{ Containers: []api.Container{ { Name: name, Image: image, VolumeMounts: volumeMounts, }, }, Volumes: volumes, } if service.ImagePullSecret != "" { pod.ImagePullSecrets = []api.LocalObjectReference{ { Name: service.ImagePullSecret, }, } } return pod } // InitSvc initializes Kubernetes Service object // The created service name will = ServiceConfig.Name, but the selector may be not. // If this service is grouped, the selector may be another name = name func (k *Kubernetes) InitSvc(name string, service kobject.ServiceConfig) *api.Service { svc := &api.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: service.Name, Labels: transformer.ConfigLabels(name), }, // The selector uses the service.Name, which must be consistent with workloads label Spec: api.ServiceSpec{ Selector: transformer.ConfigLabels(name), }, } return svc } // InitConfigMapForEnvWithLookup initializes a ConfigMap object from an env_file with variable interpolation support // using the provided lookup function to resolve variable references like ${VAR} or ${VAR:-default} func (k *Kubernetes) InitConfigMapForEnvWithLookup(name string, opt kobject.ConvertOptions, envFile string, lookup func(key string) (string, bool)) *api.ConfigMap { workDir, err := transformer.GetComposeFileDir(opt.InputFiles) if err != nil { log.Fatalf("Unable to get compose file directory: %s", err) } envs, err := LoadEnvFiles(filepath.Join(workDir, envFile), lookup) if err != nil { log.Fatalf("Unable to retrieve env file: %s", err) } // Remove root pathing // replace all other slashes / periods envName := FormatEnvName(envFile, name) // In order to differentiate files, we append to the name and remove '.env' if applicable from the file name configMap := &api.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: envName, Labels: transformer.ConfigLabels(name + "-" + envName), }, Data: envs, } return configMap } // InitConfigMapForEnv initializes a ConfigMap object func (k *Kubernetes) InitConfigMapForEnv(name string, opt kobject.ConvertOptions, envFile string) *api.ConfigMap { workDir, err := transformer.GetComposeFileDir(opt.InputFiles) if err != nil { log.Fatalf("Unable to get compose file directory: %s", err) } envs, err := GetEnvsFromFile(filepath.Join(workDir, envFile)) if err != nil { log.Fatalf("Unable to retrieve env file: %s", err) } // Remove root pathing // replace all other slashes / periods envName := FormatEnvName(envFile, name) // In order to differentiate files, we append to the name and remove '.env' if applicable from the file name configMap := &api.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: envName, Labels: transformer.ConfigLabels(name + "-" + envName), }, Data: envs, } return configMap } // IntiConfigMapFromFileOrDir will create a configmap from dir or file // usage: // 1. volume func (k *Kubernetes) IntiConfigMapFromFileOrDir(name, cmName, filePath string, service kobject.ServiceConfig) (*api.ConfigMap, error) { configMap := &api.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: cmName, Labels: transformer.ConfigLabels(name), }, } dataMap := make(map[string]string) fi, err := os.Stat(filePath) if err != nil { return nil, err } switch mode := fi.Mode(); { case mode.IsDir(): files, err := os.ReadDir(filePath) if err != nil { return nil, err } for _, file := range files { if !file.IsDir() { log.Debugf("Read file to ConfigMap: %s", file.Name()) data, err := GetContentFromFile(filePath + "/" + file.Name()) if err != nil { return nil, err } dataMap[file.Name()] = data } } initConfigMapData(configMap, dataMap) case mode.IsRegular(): // do file stuff configMap = k.InitConfigMapFromFile(name, service, filePath) configMap.Name = cmName configMap.Annotations = map[string]string{ "use-subpath": "true", } } return configMap, nil } // useSubPathMount check if a configmap should be mounted as subpath // in this situation, this configmap will only contains 1 key in data func useSubPathMount(cm *api.ConfigMap) bool { if cm.Annotations == nil { return false } if cm.Annotations["use-subpath"] != "true" { return false } return true } func initConfigMapData(configMap *api.ConfigMap, data map[string]string) { stringData := map[string]string{} binData := map[string][]byte{} for k, v := range data { lfText := strings.Replace(v, "\r\n", "\n", -1) isText := util.IsText([]byte(lfText)) if isText { stringData[k] = lfText } else { binData[k] = []byte(base64.StdEncoding.EncodeToString([]byte(v))) } } configMap.Data = stringData configMap.BinaryData = binData } // InitConfigMapFromContent initializes a ConfigMap object func (k *Kubernetes) InitConfigMapFromContent(name string, service kobject.ServiceConfig, content string, currentConfigName string, target string) *api.ConfigMap { configMap := &api.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: currentConfigName, Labels: transformer.ConfigLabels(name), }, } filename := GetFileName(target) data := map[string]string{filename: content} initConfigMapData(configMap, data) return configMap } // InitConfigMapFromFile initializes a ConfigMap object func (k *Kubernetes) InitConfigMapFromFile(name string, service kobject.ServiceConfig, fileName string) *api.ConfigMap { content, err := GetContentFromFile(fileName) if err != nil { log.Fatalf("Unable to retrieve file: %s", err) } configMapName := "" for key, tmpConfig := range service.ConfigsMetaData { if tmpConfig.File == fileName { configMapName = key } } configMap := &api.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: FormatFileName(configMapName), Labels: transformer.ConfigLabels(name), }, } data := map[string]string{filepath.Base(fileName): content} initConfigMapData(configMap, data) return configMap } // InitD initializes Kubernetes Deployment object func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int) *appsv1.Deployment { var podSpec api.PodSpec if len(service.Configs) > 0 { podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service) } else { podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret) } rp := int32(replicas) dc := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigAllLabels(name, &service), }, Spec: appsv1.DeploymentSpec{ Replicas: &rp, Selector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(name), }, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ //Labels: transformer.ConfigLabels(name), Annotations: transformer.ConfigAnnotations(service), }, Spec: podSpec, }, }, } dc.Spec.Template.Labels = transformer.ConfigLabels(name) update := service.GetKubernetesUpdateStrategy() if update != nil { dc.Spec.Strategy = appsv1.DeploymentStrategy{ Type: appsv1.RollingUpdateDeploymentStrategyType, RollingUpdate: update, } ms := "" if update.MaxSurge != nil { ms = update.MaxSurge.String() } mu := "" if update.MaxUnavailable != nil { mu = update.MaxUnavailable.String() } log.Debugf("Set deployment '%s' rolling update: MaxSurge: %s, MaxUnavailable: %s", name, ms, mu) } return dc } // InitDS initializes Kubernetes DaemonSet object func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.DaemonSet { ds := &appsv1.DaemonSet{ TypeMeta: metav1.TypeMeta{ Kind: "DaemonSet", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigAllLabels(name, &service), }, Spec: appsv1.DaemonSetSpec{ Selector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(name), }, Template: api.PodTemplateSpec{ Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret), }, }, } return ds } // InitSS method initialize a stateful set func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int) *appsv1.StatefulSet { var podSpec api.PodSpec if len(service.Configs) > 0 { podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service) } else { podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret) } rp := int32(replicas) ds := &appsv1.StatefulSet{ TypeMeta: metav1.TypeMeta{ Kind: "StatefulSet", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigAllLabels(name, &service), }, Spec: appsv1.StatefulSetSpec{ Replicas: &rp, Template: api.PodTemplateSpec{ Spec: podSpec, }, Selector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(name), }, ServiceName: service.Name, }, } return ds } // InitCJ initializes Kubernetes CronJob object func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule string, concurrencyPolicy batchv1.ConcurrencyPolicy, backoffLimit *int32) *batchv1.CronJob { cj := &batchv1.CronJob{ TypeMeta: metav1.TypeMeta{ Kind: "CronJob", APIVersion: "batch/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigAllLabels(name, &service), }, Spec: batchv1.CronJobSpec{ Schedule: schedule, ConcurrencyPolicy: concurrencyPolicy, JobTemplate: batchv1.JobTemplateSpec{ Spec: batchv1.JobSpec{ BackoffLimit: backoffLimit, Template: api.PodTemplateSpec{ Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret), }, }, }, }, } return cj } func (k *Kubernetes) initIngress(name string, service kobject.ServiceConfig, port int32) *networkingv1.Ingress { hosts := regexp.MustCompile("[ ,]*,[ ,]*").Split(service.ExposeService, -1) ingress := &networkingv1.Ingress{ TypeMeta: metav1.TypeMeta{ Kind: "Ingress", APIVersion: "networking.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), Annotations: transformer.ConfigAnnotations(service), }, Spec: networkingv1.IngressSpec{ Rules: make([]networkingv1.IngressRule, len(hosts)), }, } tlsHosts := make([]string, len(hosts)) pathType := networkingv1.PathTypePrefix for i, host := range hosts { host, p := transformer.ParseIngressPath(host) if p == "" { p = "/" } ingress.Spec.Rules[i] = networkingv1.IngressRule{ IngressRuleValue: networkingv1.IngressRuleValue{ HTTP: &networkingv1.HTTPIngressRuleValue{ Paths: []networkingv1.HTTPIngressPath{ { Path: p, PathType: &pathType, Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: name, Port: networkingv1.ServiceBackendPort{ Number: port, }, }, }, }, }, }, }, } if host != "true" { ingress.Spec.Rules[i].Host = host tlsHosts[i] = host } } if service.ExposeServiceTLS != "" { if service.ExposeServiceTLS != "true" { ingress.Spec.TLS = []networkingv1.IngressTLS{ { Hosts: tlsHosts, SecretName: service.ExposeServiceTLS, }, } } else { ingress.Spec.TLS = []networkingv1.IngressTLS{ { Hosts: tlsHosts, }, } } } if service.ExposeServiceIngressClassName != "" { ingress.Spec.IngressClassName = &service.ExposeServiceIngressClassName } return ingress } // CreateSecrets create secrets func (k *Kubernetes) CreateSecrets(komposeObject kobject.KomposeObject) ([]*api.Secret, error) { var objects []*api.Secret for name, config := range komposeObject.Secrets { if config.File != "" { dataString, err := GetContentFromFile(config.File) if err != nil { log.Fatal("unable to read secret from file: ", config.File) return nil, err } data := []byte(dataString) resourceName := FormatResourceName(name) secret := &api.Secret{ TypeMeta: metav1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: resourceName, Labels: transformer.ConfigLabels(resourceName), }, Type: api.SecretTypeOpaque, Data: map[string][]byte{resourceName: data}, } objects = append(objects, secret) } else { log.Warnf("External secrets %s is not currently supported - ignoring", name) } } return objects, nil } // CreatePVC initializes PersistentVolumeClaim func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string) (*api.PersistentVolumeClaim, error) { volSize, err := resource.ParseQuantity(size) if err != nil { return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size") } pvc := &api.PersistentVolumeClaim{ TypeMeta: metav1.TypeMeta{ Kind: "PersistentVolumeClaim", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: FormatResourceName(name), Labels: transformer.ConfigLabels(name), }, Spec: api.PersistentVolumeClaimSpec{ Resources: api.VolumeResourceRequirements{ Requests: api.ResourceList{ api.ResourceStorage: volSize, }, }, }, } if len(selectorValue) > 0 { pvc.Spec.Selector = &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(selectorValue), } } pvc.Spec.AccessModes = setVolumeAccessMode(mode, pvc.Spec.AccessModes) if len(storageClassName) > 0 { pvc.Spec.StorageClassName = &storageClassName } return pvc, nil } // ConfigPorts configures the container ports. func ConfigPorts(service kobject.ServiceConfig) []api.ContainerPort { var ports []api.ContainerPort exist := map[string]bool{} for _, port := range service.Port { if exist[port.ID()] { continue } containerPort := api.ContainerPort{ ContainerPort: port.ContainerPort, Protocol: api.Protocol(port.Protocol), } if service.ExposeContainerToHost { containerPort.HostIP = port.HostIP containerPort.HostPort = port.HostPort } ports = append(ports, containerPort) exist[port.ID()] = true } return ports } // ConfigLBServicePorts method configure the ports of the k8s Load Balancer Service func (k *Kubernetes) ConfigLBServicePorts(service kobject.ServiceConfig) ([]api.ServicePort, []api.ServicePort) { var tcpPorts []api.ServicePort var udpPorts []api.ServicePort for _, port := range service.Port { if port.HostPort == 0 { port.HostPort = port.ContainerPort } var targetPort intstr.IntOrString targetPort.IntVal = port.ContainerPort targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) servicePort := api.ServicePort{ Name: strconv.Itoa(int(port.HostPort)), Port: port.HostPort, TargetPort: targetPort, } if protocol := api.Protocol(port.Protocol); protocol == api.ProtocolTCP { // If the default is already TCP, no need to include protocol. tcpPorts = append(tcpPorts, servicePort) } else { servicePort.Protocol = protocol udpPorts = append(udpPorts, servicePort) } } return tcpPorts, udpPorts } // ConfigServicePorts configure the container service ports. func (k *Kubernetes) ConfigServicePorts(service kobject.ServiceConfig) []api.ServicePort { servicePorts := []api.ServicePort{} seenPorts := make(map[int]struct{}, len(service.Port)) var servicePort api.ServicePort for _, port := range service.Port { if port.HostPort == 0 { port.HostPort = port.ContainerPort } var targetPort intstr.IntOrString targetPort.IntVal = port.ContainerPort targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) // decide the name based on whether we saw this port before name := strconv.Itoa(int(port.HostPort)) if _, ok := seenPorts[int(port.HostPort)]; ok { // https://github.com/kubernetes/kubernetes/issues/2995 if service.ServiceType == string(api.ServiceTypeLoadBalancer) { log.Fatalf("Service %s of type LoadBalancer cannot use TCP and UDP for the same port", name) } name = fmt.Sprintf("%s-%s", name, strings.ToLower(port.Protocol)) } servicePort = api.ServicePort{ Name: name, Port: port.HostPort, TargetPort: targetPort, } if service.ServiceType == string(api.ServiceTypeNodePort) && service.NodePortPort != 0 { servicePort.NodePort = service.NodePortPort } // If the default is already TCP, no need to include protocol. if protocol := api.Protocol(port.Protocol); protocol != api.ProtocolTCP { servicePort.Protocol = protocol } servicePorts = append(servicePorts, servicePort) seenPorts[int(port.HostPort)] = struct{}{} } return servicePorts } // ConfigCapabilities configure POSIX capabilities that can be added or removed to a container func ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities { capsAdd := []api.Capability{} capsDrop := []api.Capability{} for _, capAdd := range service.CapAdd { capsAdd = append(capsAdd, api.Capability(capAdd)) } for _, capDrop := range service.CapDrop { capsDrop = append(capsDrop, api.Capability(capDrop)) } return &api.Capabilities{ Add: capsAdd, Drop: capsDrop, } } // ConfigTmpfs configure the tmpfs. func (k *Kubernetes) ConfigTmpfs(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { //initializing volumemounts and volumes volumeMounts := []api.VolumeMount{} volumes := []api.Volume{} for index, volume := range service.TmpFs { //naming volumes if multiple tmpfs are provided volumeName := fmt.Sprintf("%s-tmpfs%d", name, index) volume = strings.Split(volume, ":")[0] // create a new volume mount object and append to list volMount := api.VolumeMount{ Name: volumeName, MountPath: volume, } volumeMounts = append(volumeMounts, volMount) //create tmpfs specific empty volumes volSource := k.ConfigEmptyVolumeSource("tmpfs") // create a new volume object using the volsource and add to list vol := api.Volume{ Name: volumeName, VolumeSource: *volSource, } volumes = append(volumes, vol) } return volumeMounts, volumes } // ConfigSecretVolumes config volumes from secret. // Link: https://docs.docker.com/compose/compose-file/#secrets // In kubernetes' Secret resource, it has a data structure like a map[string]bytes, every key will act like the file name // when mount to a container. This is the part that missing in compose. So we will create a single key secret from compose // config and the key's name will be the secret's name, it's value is the file content. // compose's secret can only be mounted at `/run/secrets`, so this will be hardcoded. func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { var volumeMounts []api.VolumeMount var volumes []api.Volume if len(service.Secrets) > 0 { for _, secretConfig := range service.Secrets { secretConfig := reformatSecretConfigUnderscoreWithDash(secretConfig) if secretConfig.UID != "" { log.Warnf("Ignore pid in secrets for service: %s", name) } if secretConfig.GID != "" { log.Warnf("Ignore gid in secrets for service: %s", name) } var secretItemPath, secretMountPath, secretSubPath string if k.Opt.SecretsAsFiles { secretItemPath, secretMountPath, secretSubPath = k.getSecretPaths(secretConfig) } else { secretItemPath, secretMountPath, secretSubPath = k.getSecretPathsLegacy(secretConfig) } volSource := api.VolumeSource{ Secret: &api.SecretVolumeSource{ SecretName: secretConfig.Source, Items: []api.KeyToPath{{ Key: secretConfig.Source, Path: secretItemPath, }}, }, } if secretConfig.Mode != nil { mode := cast.ToInt32(*secretConfig.Mode) volSource.Secret.DefaultMode = &mode } vol := api.Volume{ Name: secretConfig.Source, VolumeSource: volSource, } volumes = append(volumes, vol) volMount := api.VolumeMount{ Name: vol.Name, MountPath: secretMountPath, SubPath: secretSubPath, } volumeMounts = append(volumeMounts, volMount) } } return volumeMounts, volumes } func (k *Kubernetes) getSecretPaths(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) { // Default secretConfig.Target to secretConfig.Source, just in case user was using short secret syntax or // otherwise did not define a specific target target := secretConfig.Target if target == "" { target = secretConfig.Source } // If target is an absolute path, set that as the MountPath if strings.HasPrefix(secretConfig.Target, "/") { secretMountPath = target } else { // If target is a relative path, prefix with "/run/secrets/" to replicate what docker-compose would do secretMountPath = "/run/secrets/" + target } // Set subPath to the target filename. this ensures that we end up with a file at our MountPath instead // of a directory with symlinks (see https://stackoverflow.com/a/68332231) splitPath := strings.Split(target, "/") secretFilename := splitPath[len(splitPath)-1] // `secretItemPath` and `secretSubPath` have to be the same as `secretFilename` to ensure we create a file with // that name at `secretMountPath`, instead of a directory containing a symlink to the actual file. secretItemPath = secretFilename secretSubPath = secretFilename return secretItemPath, secretMountPath, secretSubPath } func (k *Kubernetes) getSecretPathsLegacy(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) { // The old way of setting secret paths. It resulted in files being placed in incorrect locations when compared to // docker-compose results, but some people might depend on this behavior so this is kept here for compatibility. // See https://github.com/kubernetes/kompose/issues/1280 for more details. var itemPath string // should be the filename var mountPath = "" // should be the directory // if is used the short-syntax if secretConfig.Target == "" { // the secret path (mountPath) should be inside the default directory /run/secrets mountPath = "/run/secrets/" + secretConfig.Source // the itemPath should be the source itself itemPath = secretConfig.Source } else { // if is the long-syntax, i should get the last part of path and consider it the filename pathSplitted := strings.Split(secretConfig.Target, "/") lastPart := pathSplitted[len(pathSplitted)-1] // if the filename (lastPart) and the target is the same if lastPart == secretConfig.Target { // the secret path should be the source (it need to be inside a directory and only the filename was given) mountPath = secretConfig.Source } else { // should then get the target without the filename (lastPart) mountPath = mountPath + strings.TrimSuffix(secretConfig.Target, "/"+lastPart) // menos ultima parte } // if the target isn't absolute path if !strings.HasPrefix(secretConfig.Target, "/") { // concat the default secret directory mountPath = "/run/secrets/" + mountPath } itemPath = lastPart } secretSubPath = itemPath //"" // We didn't set a SubPath in legacy behavior return itemPath, mountPath, secretSubPath } // ConfigVolumes configure the container volumes. func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) { volumeMounts := []api.VolumeMount{} volumes := []api.Volume{} var PVCs []*api.PersistentVolumeClaim var cms []*api.ConfigMap var volumeName string var subpathName string // Set a var based on if the user wants to use empty volumes // as opposed to persistent volumes and volume claims useEmptyVolumes := k.Opt.EmptyVols useHostPath := k.Opt.Volumes == "hostPath" useConfigMap := k.Opt.Volumes == "configMap" if k.Opt.Volumes == "emptyDir" { useEmptyVolumes = true } if subpath, ok := service.Labels["kompose.volume.subpath"]; ok { subpathName = subpath } // Override volume type if specified in service labels. if vt, ok := service.Labels["kompose.volume.type"]; ok { if _, okk := ValidVolumeSet[vt]; !okk { return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label 'kompose.volume.type' in service %s", vt, service.Name) } useEmptyVolumes = vt == "emptyDir" useHostPath = vt == "hostPath" useConfigMap = vt == "configMap" } // config volumes from secret if present secretsVolumeMounts, secretsVolumes := k.ConfigSecretVolumes(name, service) volumeMounts = append(volumeMounts, secretsVolumeMounts...) volumes = append(volumes, secretsVolumes...) var count int skip := false //iterating over array of `Vols` struct as it contains all necessary information about volumes for _, volume := range service.Volumes { // check if ro/rw mode is defined, default rw readonly := len(volume.Mode) > 0 && (volume.Mode == "ro" || volume.Mode == "rox") mountHost := volume.Host if mountHost == "" { mountHost = volume.MountPath } // return useconfigmap and readonly, // not used asigned readonly because dont break e2e useConfigMap, _, skip = isConfigFile(mountHost) if skip { log.Warnf("Skip file in path %s ", volume.Host) continue } if volume.VolumeName == "" { if useEmptyVolumes { volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1) } else if useHostPath { volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1) } else if useConfigMap { volumeName = strings.Replace(volume.PVCName, "claim", "cm", 1) } else { volumeName = volume.PVCName } // to support service group bases on volume, we need use the new group name to replace the origin service name // in volume name. For normal service, this should have no effect volumeName = strings.Replace(volumeName, service.Name, name, 1) count++ } else { volumeName = volume.VolumeName } volMount := api.VolumeMount{ Name: volumeName, ReadOnly: readonly, MountPath: volume.Container, } // Get a volume source based on the type of volume we are using // For PVC we will also create a PVC object and add to list var volsource *api.VolumeSource if useEmptyVolumes { volsource = k.ConfigEmptyVolumeSource("volume") } else if useHostPath { source, err := k.ConfigHostPathVolumeSource(volume.Host) if err != nil { return nil, nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed") } volsource = source } else if useConfigMap { log.Debugf("Use configmap volume") cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service) if err != nil { return nil, nil, nil, nil, err } cms = append(cms, cm) volsource = k.ConfigConfigMapVolumeSource(volumeName, volume.Container, cm) if useSubPathMount(cm) { volMount.SubPath = volsource.ConfigMap.Items[0].Path } } else { volsource = k.ConfigPVCVolumeSource(volumeName, readonly) if volume.VFrom == "" { var storageClassName string defaultSize := PVCRequestSize if k.Opt.PVCRequestSize != "" { defaultSize = k.Opt.PVCRequestSize } if len(volume.PVCSize) > 0 { defaultSize = volume.PVCSize } else { for key, value := range service.Labels { if key == "kompose.volume.size" { defaultSize = value } else if key == "kompose.volume.storage-class-name" { storageClassName = value } } } createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName) if err != nil { return nil, nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed") } PVCs = append(PVCs, createdPVC) } } if subpathName != "" { volMount.SubPath = subpathName } volumeMounts = append(volumeMounts, volMount) // create a new volume object using the volsource and add to list vol := api.Volume{ Name: volumeName, VolumeSource: *volsource, } volumes = append(volumes, vol) if len(volume.Host) > 0 && (!useHostPath && !useConfigMap) { log.Warningf("Volume mount on the host %q isn't supported - ignoring path on the host", volume.Host) } } return volumeMounts, volumes, PVCs, cms, nil } // ConfigEmptyVolumeSource is helper function to create an EmptyDir api.VolumeSource // either for Tmpfs or for emptyvolumes func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource { //if key is tmpfs if key == "tmpfs" { return &api.VolumeSource{ EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumMemory}, } } //if key is volume return &api.VolumeSource{ EmptyDir: &api.EmptyDirVolumeSource{}, } } // ConfigConfigMapVolumeSource config a configmap to use as volume source func (k *Kubernetes) ConfigConfigMapVolumeSource(cmName string, targetPath string, cm *api.ConfigMap) *api.VolumeSource { s := api.ConfigMapVolumeSource{} s.Name = cmName if useSubPathMount(cm) { var keys []string for k := range cm.Data { keys = append(keys, k) } for k := range cm.BinaryData { keys = append(keys, k) } key := keys[0] _, p := path.Split(targetPath) s.Items = []api.KeyToPath{ { Key: key, Path: p, }, } } return &api.VolumeSource{ ConfigMap: &s, } } // ConfigHostPathVolumeSource is a helper function to create a HostPath api.VolumeSource func (k *Kubernetes) ConfigHostPathVolumeSource(path string) (*api.VolumeSource, error) { dir, err := transformer.GetComposeFileDir(k.Opt.InputFiles) if err != nil { return nil, err } absPath := path if !filepath.IsAbs(path) { absPath = filepath.Join(dir, path) } return &api.VolumeSource{ HostPath: &api.HostPathVolumeSource{Path: absPath}, }, nil } // ConfigPVCVolumeSource is helper function to create an api.VolumeSource with a PVC func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.VolumeSource { return &api.VolumeSource{ PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ ClaimName: name, ReadOnly: readonly, }, } } // ConfigEnvs configures the environment variables. func ConfigEnvs(service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, []api.EnvFromSource, error) { envs := transformer.EnvSort{} envsFrom := []api.EnvFromSource{} keysFromEnvFile := make(map[string]bool) // If there is an env_file, use ConfigMaps and add them using EnvFrom if len(service.EnvFile) > 0 { // Load each env_file for _, file := range service.EnvFile { envName := FormatEnvName(file, service.Name) envsFrom = append(envsFrom, api.EnvFromSource{ ConfigMapRef: &api.ConfigMapEnvSource{ LocalObjectReference: api.LocalObjectReference{ Name: envName, }, }, }) // Load environment variables from file workDir, err := transformer.GetComposeFileDir(opt.InputFiles) if err != nil { log.Fatalf("Unable to get compose file directory: %s", err) } envLoad, err := GetEnvsFromFile(filepath.Join(workDir, file)) if err != nil { return envs, envsFrom, errors.Wrap(err, "Unable to read env_file") } // Mark environment variable source to env file for k := range envLoad { keysFromEnvFile[k] = true } } } // Load up the environment variables for _, v := range service.Environment { if !keysFromEnvFile[v.Name] { if strings.Contains(v.Value, "run/secrets") { v.Value = FormatResourceName(v.Value) } envs = append(envs, api.EnvVar{ Name: v.Name, Value: v.Value, }) } } // Stable sorts data while keeping the original order of equal elements // we need this because envs are not populated in any random order // this sorting ensures they are populated in a particular order sort.Stable(envs) return envs, envsFrom, nil } // ConfigAffinity configures the Affinity. func ConfigAffinity(service kobject.ServiceConfig) *api.Affinity { var affinity *api.Affinity // Config constraints // Convert constraints to requiredDuringSchedulingIgnoredDuringExecution positiveConstraints := configConstrains(service.Placement.PositiveConstraints, api.NodeSelectorOpIn) negativeConstraints := configConstrains(service.Placement.NegativeConstraints, api.NodeSelectorOpNotIn) if len(positiveConstraints) != 0 || len(negativeConstraints) != 0 { affinity = &api.Affinity{ NodeAffinity: &api.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ NodeSelectorTerms: []api.NodeSelectorTerm{ { MatchExpressions: append(positiveConstraints, negativeConstraints...), }, }, }, }, } } return affinity } // ConfigTopologySpreadConstraints configures the TopologySpreadConstraints. func ConfigTopologySpreadConstraints(service kobject.ServiceConfig) []api.TopologySpreadConstraint { preferencesLen := len(service.Placement.Preferences) constraints := make([]api.TopologySpreadConstraint, 0, preferencesLen) // Placement preferences are ignored for global services if service.DeployMode == "global" { log.Warnf("Ignore placement preferences for global service %s", service.Name) return constraints } for i, p := range service.Placement.Preferences { constraints = append(constraints, api.TopologySpreadConstraint{ // According to the order of preferences, the MaxSkew decreases in order // The minimum value is 1 MaxSkew: int32(preferencesLen - i), TopologyKey: p, WhenUnsatisfiable: api.ScheduleAnyway, LabelSelector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(service.Name), }, }) } return constraints } func configConstrains(constrains map[string]string, operator api.NodeSelectorOperator) []api.NodeSelectorRequirement { constraintsLen := len(constrains) rs := make([]api.NodeSelectorRequirement, 0, constraintsLen) if constraintsLen == 0 { return rs } for k, v := range constrains { r := api.NodeSelectorRequirement{ Key: k, Operator: operator, Values: []string{v}, } rs = append(rs, r) } return rs } // CreateWorkloadAndConfigMapObjects generates a Kubernetes artifact for each input type service func (k *Kubernetes) CreateWorkloadAndConfigMapObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) []runtime.Object { var objects []runtime.Object var replica int if opt.IsReplicaSetFlag || service.Replicas == 0 { replica = opt.Replicas } else { replica = service.Replicas } // Check to see if Compose v3 Deploy.Mode has been set to "global" if service.DeployMode == "global" { //default use daemonset if opt.Controller == "" { opt.CreateD = false opt.CreateDS = true } else if opt.Controller != "daemonset" { log.Warnf("Global deploy mode service is best converted to daemonset, now it convert to %s", opt.Controller) } } //Resolve labels first if val, ok := service.Labels[compose.LabelControllerType]; ok { opt.CreateD = false opt.CreateDS = false opt.CreateRC = false if opt.Controller != "" { log.Warnf("Use label %s type %s for service %s, ignore %s flags", compose.LabelControllerType, val, name, opt.Controller) } opt.Controller = val } if len(service.Configs) > 0 { objects = k.createConfigMapFromComposeConfig(name, service, objects) } if opt.CreateD || opt.Controller == DeploymentController { objects = append(objects, k.InitD(name, service, replica)) } if opt.CreateDS || opt.Controller == DaemonSetController { objects = append(objects, k.InitDS(name, service)) } if opt.Controller == StatefulStateController { objects = append(objects, k.InitSS(name, service, replica)) } envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt) objects = append(objects, envConfigMaps...) return objects } func (k *Kubernetes) createConfigMapFromComposeConfig(name string, service kobject.ServiceConfig, objects []runtime.Object) []runtime.Object { for _, config := range service.Configs { currentConfigName := config.Source currentConfigObj := service.ConfigsMetaData[currentConfigName] if config.Target == "" { config.Target = currentConfigName } if currentConfigObj.External { continue } if currentConfigObj.File != "" { currentFileName := currentConfigObj.File configMap := k.InitConfigMapFromFile(name, service, currentFileName) objects = append(objects, configMap) } else if currentConfigObj.Content != "" { content := currentConfigObj.Content configMap := k.InitConfigMapFromContent(name, service, content, currentConfigName, config.Target) objects = append(objects, configMap) } else if currentConfigObj.Environment != "" { // TODO: Add support for environment variables in configmaps log.Warnf("Environment variables in configmaps are not supported yet") } else { log.Warnf("Configmap %s is empty", currentConfigName) } } return objects } // InitPod initializes Kubernetes Pod object func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Pod { pod := api.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), Annotations: transformer.ConfigAnnotations(service), }, Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret), } return &pod } // CreateNetworkPolicy initializes Network policy func (k *Kubernetes) CreateNetworkPolicy(networkName string) (*networkingv1.NetworkPolicy, error) { str := "true" np := &networkingv1.NetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: "NetworkPolicy", APIVersion: "networking.k8s.io/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: networkName, //Labels: transformer.ConfigLabels(name)(name), }, Spec: networkingv1.NetworkPolicySpec{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{"io.kompose.network/" + networkName: str}, }, Ingress: []networkingv1.NetworkPolicyIngressRule{{ From: []networkingv1.NetworkPolicyPeer{{ PodSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"io.kompose.network/" + networkName: str}, }, }}, }}, }, } return np, nil } func buildServiceImage(opt kobject.ConvertOptions, service kobject.ServiceConfig, name string) error { // Must build the images before conversion (got to add service.Image in case 'image' key isn't provided // Check that --build is set to true // Check to see if there is an InputFile (required!) before we build the container // Check that there's actually a Build key // Lastly, we must have an Image name to continue // If the user provided a custom build it will override the docker one. if opt.BuildCommand != "" && opt.PushCommand != "" { p := shellwords.NewParser() p.ParseEnv = true buildArgs, _ := p.Parse(opt.BuildCommand) buildCommand := exec.Command(buildArgs[0], buildArgs[1:]...) err := buildCommand.Run() if err != nil { return errors.Wrap(err, "error while trying to build a custom container image") } pushArgs, _ := p.Parse(opt.PushCommand) pushCommand := exec.Command(pushArgs[0], pushArgs[1:]...) err = pushCommand.Run() if err != nil { return errors.Wrap(err, "error while trying to push a custom container image") } return nil } if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" { // If there's no "image" key, use the name of the container that's built if service.Image == "" { service.Image = name } if service.Image == "" { return fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name) } log.Infof("Build key detected. Attempting to build image '%s'", service.Image) // Build the image! err := transformer.BuildDockerImage(service, name) if err != nil { return errors.Wrapf(err, "Unable to build Docker image for service %v", name) } // Push the built image to the repo! err = transformer.PushDockerImageWithOpt(service, name, opt) if err != nil { return errors.Wrapf(err, "Unable to push Docker image for service %v", name) } } return nil } func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) { if k.PortsExist(service) { if service.ServiceType == "LoadBalancer" { svcs := k.CreateLBService(name, service) for _, svc := range svcs { svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy) *objects = append(*objects, svc) } if len(svcs) > 1 { log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalancer type") } } else { svc := k.CreateService(name, service) *objects = append(*objects, svc) if service.ExposeService != "" { *objects = append(*objects, k.initIngress(name, service, svc.Spec.Ports[0].Port)) } if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != api.ServiceTypeNodePort { log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType) } } } else { if service.ServiceType == "Headless" { svc := k.CreateHeadlessService(name, service) *objects = append(*objects, svc) if service.ServiceExternalTrafficPolicy != "" { log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name) } } else { log.Warnf("Service %q won't be created because 'ports' is not specified", service.Name) } } } func (k *Kubernetes) configNetworkPolicyForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) error { if len(service.Network) > 0 { for _, net := range service.Network { log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net) np, err := k.CreateNetworkPolicy(net) if err != nil { return errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name) } *objects = append(*objects, np) } } return nil } // Transform maps komposeObject to k8s objects // returns object that are already sorted in the way that Services are first func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) ([]runtime.Object, error) { // this will hold all the converted data var allobjects []runtime.Object if komposeObject.Secrets != nil { secrets, err := k.CreateSecrets(komposeObject) if err != nil { return nil, errors.Wrapf(err, "Unable to create Secret resource") } for _, item := range secrets { allobjects = append(allobjects, item) } } if komposeObject.Namespace != "" { ns := transformer.CreateNamespace(komposeObject.Namespace) allobjects = append(allobjects, ns) } if opt.ServiceGroupMode != "" { log.Debugf("Service group mode is: %s", opt.ServiceGroupMode) komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(&komposeObject, opt) sortedGroupMappingKeys := SortedKeys(komposeObjectToServiceConfigGroupMapping) for _, group := range sortedGroupMappingKeys { groupMapping := komposeObjectToServiceConfigGroupMapping[group] var objects []runtime.Object podSpec := PodSpec{} var groupName string // if using volume group, the name here will be a volume config string. reset to the first service name if opt.ServiceGroupMode == "volume" { if opt.ServiceGroupName != "" { groupName = opt.ServiceGroupName } else { var names []string for _, svc := range groupMapping { names = append(names, svc.Name) } groupName = strings.Join(names, "-") } } else { groupName = group } // added a container // ports conflict check between services portsUses := map[string]bool{} for _, service := range groupMapping { // first do ports check ports := ConfigPorts(service) for _, port := range ports { key := string(port.ContainerPort) + string(port.Protocol) if portsUses[key] { return nil, fmt.Errorf("detect ports conflict when group services, service: %s, port: %d", service.Name, port.ContainerPort) } portsUses[key] = true } log.Infof("Group Service %s to [%s]", service.Name, groupName) service.WithKomposeAnnotation = opt.WithKomposeAnnotation podSpec.Append(AddContainer(service, opt)) if err := buildServiceImage(opt, service, service.Name); err != nil { return nil, err } // override.. objects = append(objects, k.CreateWorkloadAndConfigMapObjects(groupName, service, opt)...) k.configKubeServiceAndIngressForService(service, groupName, &objects) // Configure the container volumes. volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service) if err != nil { return nil, errors.Wrap(err, "k.ConfigVolumes failed") } // Configure Tmpfs if len(service.TmpFs) > 0 { TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(groupName, service) volumes = append(volumes, TmpVolumes...) volumesMount = append(volumesMount, TmpVolumesMount...) } podSpec.Append( SetVolumeMounts(volumesMount), SetVolumes(volumes), ) // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)` // because the type of objects and pvc is different, but when doing append // one element at a time it gets converted to runtime.Object for objects slice for _, p := range pvc { objects = append(objects, p) } for _, c := range cms { objects = append(objects, c) } podSpec.Append( SetPorts(service), ImagePullPolicy(groupName, service), RestartPolicy(groupName, service), SecurityContext(groupName, service), HostName(service), DomainName(service), ResourcesLimits(service), ResourcesRequests(service), TerminationGracePeriodSeconds(groupName, service), TopologySpreadConstraints(service), ) if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok { podSpec.Append(ServiceAccountName(serviceAccountName)) } err = k.UpdateKubernetesObjectsMultipleContainers(groupName, service, &objects, podSpec, opt) if err != nil { return nil, errors.Wrap(err, "Error transforming Kubernetes objects") } if opt.GenerateNetworkPolicies { if err = k.configNetworkPolicyForService(service, service.Name, &objects); err != nil { return nil, err } } } allobjects = append(allobjects, objects...) } } sortedKeys := SortedKeys(komposeObject.ServiceConfigs) for _, name := range sortedKeys { service := komposeObject.ServiceConfigs[name] // if service belongs to a group, we already processed it if service.InGroup { continue } var objects []runtime.Object service.WithKomposeAnnotation = opt.WithKomposeAnnotation if err := buildServiceImage(opt, service, name); err != nil { return nil, err } // Generate pod or cronjob and configmap objects if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() { if service.CronJobSchedule != "" { log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart) cronJob := k.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit) objects = append(objects, cronJob) } else { pod := k.InitPod(name, service) objects = append(objects, pod) } envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt) objects = append(objects, envConfigMaps...) } else { objects = k.CreateWorkloadAndConfigMapObjects(name, service, opt) } if opt.Controller == StatefulStateController { service.ServiceType = "Headless" } k.configKubeServiceAndIngressForService(service, name, &objects) err := k.UpdateKubernetesObjects(name, service, opt, &objects) if err != nil { return nil, errors.Wrap(err, "Error transforming Kubernetes objects") } if opt.GenerateNetworkPolicies { if err := k.configNetworkPolicyForService(service, name, &objects); err != nil { return nil, err } } err = k.configHorizontalPodScaler(name, service, opt, &objects) if err != nil { return nil, errors.Wrap(err, "Error creating Kubernetes HPA") } allobjects = append(allobjects, objects...) } // sort all object so Services are first k.SortServicesFirst(&allobjects) k.RemoveDupObjects(&allobjects) // Only append namespaces if --namespace has been passed in if komposeObject.Namespace != "" { transformer.AssignNamespaceToObjects(&allobjects, komposeObject.Namespace) } // k.FixWorkloadVersion(&allobjects) k.fixNetworkModeToService(&allobjects, komposeObject.ServiceConfigs) return allobjects, nil } // UpdateController updates the given object with the given pod template update function and ObjectMeta update function func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec) error, updateMeta func(meta *metav1.ObjectMeta)) (err error) { switch t := obj.(type) { case *appsv1.Deployment: err = updateTemplate(&t.Spec.Template) if err != nil { return errors.Wrap(err, "updateTemplate failed") } updateMeta(&t.ObjectMeta) case *appsv1.DaemonSet: err = updateTemplate(&t.Spec.Template) if err != nil { return errors.Wrap(err, "updateTemplate failed") } updateMeta(&t.ObjectMeta) case *appsv1.StatefulSet: err = updateTemplate(&t.Spec.Template) if err != nil { return errors.Wrap(err, "updateTemplate failed") } updateMeta(&t.ObjectMeta) case *batchv1.CronJob: err = updateTemplate(&t.Spec.JobTemplate.Spec.Template) if err != nil { return errors.Wrap(err, "updateTemplate failed") } updateMeta(&t.ObjectMeta) case *deployapi.DeploymentConfig: err = updateTemplate(t.Spec.Template) if err != nil { return errors.Wrap(err, "updateTemplate failed") } updateMeta(&t.ObjectMeta) case *api.Pod: p := api.PodTemplateSpec{ ObjectMeta: t.ObjectMeta, Spec: t.Spec, } err = updateTemplate(&p) if err != nil { return errors.Wrap(err, "updateTemplate failed") } t.Spec = p.Spec t.ObjectMeta = p.ObjectMeta case *buildapi.BuildConfig: updateMeta(&t.ObjectMeta) } return nil } // configHorizontalPodScaler create Hpa resource also append to the objects // first checks if the service labels contain any HPA labels using the searchHPAValues func (k *Kubernetes) configHorizontalPodScaler(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) (err error) { found := searchHPAValues(service.Labels) if !found { return nil } hpa := createHPAResources(name, &service) *objects = append(*objects, &hpa) return nil } func (k *Kubernetes) PargeEnvFiletoConfigMaps(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) []runtime.Object { envs := make(map[string]string) for _, env := range service.Environment { envs[env.Name] = env.Value } configMaps := make([]runtime.Object, 0) for _, envFile := range service.EnvFile { configMap := k.InitConfigMapForEnvWithLookup(name, opt, envFile, func(key string) (string, bool) { v, ok := envs[key] return v, ok }) configMaps = append(configMaps, configMap) } return configMaps } ================================================ FILE: pkg/transformer/kubernetes/kubernetes_test.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package kubernetes import ( "encoding/json" "fmt" "os" "path/filepath" "reflect" "slices" "strings" "testing" "github.com/compose-spec/compose-go/v2/types" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/loader/compose" "github.com/kubernetes/kompose/pkg/transformer" deployapi "github.com/openshift/api/apps/v1" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" api "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) func newServiceConfig() kobject.ServiceConfig { return kobject.ServiceConfig{ Name: "app", ContainerName: "name", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456}, {HostPort: 123, ContainerPort: 456, Protocol: string(api.ProtocolUDP)}, {HostPort: 55564, ContainerPort: 55564}, {HostPort: 55563, ContainerPort: 55563}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Labels: nil, FsGroup: 1001, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, CapDrop: []string{"cap_drop"}, Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", ImagePullSecret: "regcred", Stdin: true, Tty: true, TmpFs: []string{"/tmp"}, Replicas: 2, Volumes: []kobject.Volumes{{SvcName: "app", MountPath: "/tmp/volume", PVCName: "app-claim0"}}, GroupAdd: []int64{1003, 1005}, Configs: []types.ServiceConfigObjConfig{{Source: "config", Target: "/etc/world"}}, ConfigsMetaData: types.Configs{"config": types.ConfigObjConfig{Name: "myconfig", File: "kubernetes_test.go"}}, } } func newSimpleServiceConfig() kobject.ServiceConfig { return kobject.ServiceConfig{ Name: "app", ContainerName: "name", Image: "image", } } func newKomposeObject() kobject.KomposeObject { return kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, } } func newKomposeObjectHostPortProtocolConfig() kobject.ServiceConfig { return kobject.ServiceConfig{ Name: "nginx", ContainerName: "nginx", Image: "nginx", Port: []kobject.Ports{{HostPort: 80, Protocol: string(api.ProtocolTCP), ContainerPort: 80}}, ExposeContainerToHost: true, } } func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig { loadBalancerServiceType := string(api.ServiceTypeLoadBalancer) return kobject.ServiceConfig{ Name: "app", Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456}}, ServiceType: loadBalancerServiceType, ServiceExternalTrafficPolicy: "local", } } func newServiceConfigWithServiceVolumeMount(volumeMountSubPathValue string) kobject.ServiceConfig { return kobject.ServiceConfig{ Name: "app", VolumeMountSubPath: volumeMountSubPathValue, } } func equalEnv(kobjectEnvs []kobject.EnvVar, k8sEnvs []api.EnvVar) bool { if len(kobjectEnvs) != len(k8sEnvs) { return false } for _, env := range kobjectEnvs { found := false for _, k8sEnv := range k8sEnvs { if env.Name == k8sEnv.Name && env.Value == k8sEnv.Value { found = true } } if !found { return false } } return true } func equalPorts(kobjectPorts []kobject.Ports, k8sPorts []api.ContainerPort) bool { if len(kobjectPorts) != len(k8sPorts) { return false } for _, port := range kobjectPorts { found := false for _, k8sPort := range k8sPorts { // FIXME: HostPort should be copied to container port //if port.HostPort == k8sPort.HostPort && port.Protocol == k8sPort.Protocol && port.ContainerPort == k8sPort.ContainerPort { if port.Protocol == string(k8sPort.Protocol) && port.ContainerPort == k8sPort.ContainerPort { found = true } // Name and HostIp shouldn't be set if len(k8sPort.Name) != 0 || len(k8sPort.HostIP) != 0 { return false } } if !found { return false } } return true } func equalStringMaps(map1 map[string]string, map2 map[string]string) bool { if len(map1) != len(map2) { return false } for k, v := range map1 { if map2[k] != v { return false } } return true } func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec, expectedLabels map[string]string) error { if len(template.Spec.Containers) == 0 { return fmt.Errorf("Failed to set container: %#v vs. %#v", config, template) } container := template.Spec.Containers[0] if config.ContainerName != container.Name { return fmt.Errorf("Found different container name: %v vs. %v", config.ContainerName, container.Name) } if config.Image != container.Image { return fmt.Errorf("Found different container image: %v vs. %v", config.Image, container.Image) } if !equalEnv(config.Environment, container.Env) { return fmt.Errorf("Found different container env: %#v vs. %#v", config.Environment, container.Env) } if !equalPorts(config.Port, container.Ports) { return fmt.Errorf("Found different container ports: %#v vs. %#v", config.Port, container.Ports) } if !slices.Equal(config.Command, container.Command) { return fmt.Errorf("Found different container cmd: %#v vs. %#v", config.Command, container.Command) } if config.WorkingDir != container.WorkingDir { return fmt.Errorf("Found different container WorkingDir: %#v vs. %#v", config.WorkingDir, container.WorkingDir) } if !slices.Equal(config.Args, container.Args) { return fmt.Errorf("Found different container args: %#v vs. %#v", config.Args, container.Args) } if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || template.Spec.Volumes[0].VolumeSource.PersistentVolumeClaim == nil && template.Spec.Volumes[0].ConfigMap == nil { return fmt.Errorf("Found incorrect volumes: %v vs. %#v", config.Volumes, template.Spec.Volumes) } // We only set controller labels here and k8s server will take care of other defaults, such as selectors if !equalStringMaps(expectedLabels, template.Labels) { return fmt.Errorf("Found different template labels: %#v vs. %#v", expectedLabels, template.Labels) } restartPolicyMapping := map[string]api.RestartPolicy{"always": api.RestartPolicyAlways} if restartPolicyMapping[config.Restart] != template.Spec.RestartPolicy { return fmt.Errorf("Found incorrect restart policy: %v vs. %v", config.Restart, template.Spec.RestartPolicy) } if config.Privileged == privilegedNilOrFalse(template) { return fmt.Errorf("Found different template privileged: %#v vs. %#v", config.Privileged, template.Spec.Containers[0].SecurityContext) } if config.FsGroup != *template.Spec.SecurityContext.FSGroup { return fmt.Errorf("Found different pod security context fs group values: %#v vs. %#v", config.FsGroup, *template.Spec.SecurityContext.FSGroup) } if config.Stdin != template.Spec.Containers[0].Stdin { return fmt.Errorf("Found different values for stdin: %#v vs. %#v", config.Stdin, template.Spec.Containers[0].Stdin) } if config.Tty != template.Spec.Containers[0].TTY { return fmt.Errorf("Found different values for TTY: %#v vs. %#v", config.Tty, template.Spec.Containers[0].TTY) } if config.ImagePullSecret != template.Spec.ImagePullSecrets[0].Name { return fmt.Errorf("Found different values for ImagePullSecrets: %#v vs. %#v", config.ImagePullSecret, template.Spec.ImagePullSecrets[0].Name) } return nil } func privilegedNilOrFalse(template api.PodTemplateSpec) bool { return len(template.Spec.Containers) == 0 || template.Spec.Containers[0].SecurityContext == nil || template.Spec.Containers[0].SecurityContext.Privileged == nil || !*template.Spec.Containers[0].SecurityContext.Privileged } func checkService(config kobject.ServiceConfig, svc *api.Service, expectedLabels map[string]string) error { if !equalStringMaps(expectedLabels, svc.Spec.Selector) { return fmt.Errorf("Found unexpected selector: %#v vs. %#v", expectedLabels, svc.Spec.Selector) } for _, port := range svc.Spec.Ports { name := port.Name expectedName := strings.ToLower(name) if expectedName != name { return fmt.Errorf("Found unexpected port name: %#v vs. %#v", expectedName, name) } } // TODO: finish this return nil } func checkMeta(config kobject.ServiceConfig, meta metav1.ObjectMeta, expectedName string, shouldSetLabels bool) error { if expectedName != meta.Name { return fmt.Errorf("Found unexpected name: %s vs. %s", expectedName, meta.Name) } if shouldSetLabels != (len(meta.Labels) > 0) { return fmt.Errorf("Unexpected labels: %#v", meta.Labels) } return nil } func TestKomposeConvertIngress(t *testing.T) { testCases := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions labelValue string }{ "Convert to Ingress: label set to true": {newKomposeObject(), kobject.ConvertOptions{CreateD: true}, "true"}, "Convert to Ingress: label set to example.com": {newKomposeObject(), kobject.ConvertOptions{CreateD: true}, "example.com"}, } for name, test := range testCases { var expectedHost string t.Log("Test case:", name) k := Kubernetes{} appName := "app" // Setting value for ExposeService in ServiceConfig config := test.komposeObject.ServiceConfigs[appName] config.ExposeService = test.labelValue test.komposeObject.ServiceConfigs[appName] = config switch test.labelValue { case "true": expectedHost = "" default: expectedHost = test.labelValue } // Run Transform objs, err := k.Transform(test.komposeObject, test.opt) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Check results for _, obj := range objs { if ing, ok := obj.(*networkingv1beta1.Ingress); ok { if ing.ObjectMeta.Name != appName { t.Errorf("Expected ObjectMeta.Name to be %s, got %s instead", appName, ing.ObjectMeta.Name) } if ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.ServiceName != appName { t.Errorf("Expected Backend.ServiceName to be %s, got %s instead", appName, ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.ServiceName) } if ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.ServicePort.IntVal != config.Port[0].HostPort { t.Errorf("Expected Backend.ServicePort to be %d, got %v instead", config.Port[0].HostPort, ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Backend.ServicePort.IntVal) } if ing.Spec.Rules[0].Host != expectedHost { t.Errorf("Expected Rules[0].Host to be %s, got %s instead", expectedHost, ing.Spec.Rules[0].Host) } } } } } func TestKomposeConvert(t *testing.T) { replicas := 3 testCases := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions expectedNumObjs int }{ // objects generated are deployment, service network policies (2) and pvc "Convert to Deployments (D)": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas, IsReplicaSetFlag: true}, 4}, "Convert to Deployments (D) with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true}, 4}, "Convert to DaemonSets (DS)": {newKomposeObject(), kobject.ConvertOptions{CreateDS: true}, 4}, // objects generated are deployment, daemonset, ReplicationController, service and pvc "Convert to D, DS, and RC": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true, Replicas: replicas, IsReplicaSetFlag: true}, 5}, "Convert to D, DS, and RC with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true}, 5}, // objects generated are statefulset "Convert to SS with replicas ": {newKomposeObject(), kobject.ConvertOptions{Controller: StatefulStateController, Replicas: replicas, IsReplicaSetFlag: true}, 3}, "Convert to SS without replicas": {newKomposeObject(), kobject.ConvertOptions{Controller: StatefulStateController}, 3}, } for name, test := range testCases { t.Log("Test case:", name) k := Kubernetes{} // Run Transform objs, err := k.Transform(test.komposeObject, test.opt) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } if len(objs) != test.expectedNumObjs { t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs)) } var foundSVC, foundD, foundDS, foundDC, foundSS bool name := "app" labels := transformer.ConfigLabels(name) config := test.komposeObject.ServiceConfigs[name] labelsWithNetwork := transformer.ConfigLabelsWithNetwork(name, config.Network) // Check results for _, obj := range objs { if svc, ok := obj.(*api.Service); ok { if err := checkService(config, svc, labels); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, svc.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } foundSVC = true } if test.opt.CreateD { if d, ok := obj.(*appsv1.Deployment); ok { if err := checkPodTemplate(config, d.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, d.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if test.opt.IsReplicaSetFlag { if (int)(*d.Spec.Replicas) != replicas { t.Errorf("Expected %d replicas, got %d", replicas, d.Spec.Replicas) } } else { if (int)(*d.Spec.Replicas) != newServiceConfig().Replicas { t.Errorf("Expected %d replicas, got %d", newServiceConfig().Replicas, d.Spec.Replicas) } } foundD = true } if u, ok := obj.(*unstructured.Unstructured); ok { if u.GetKind() == "Deployment" { u.SetGroupVersionKind(schema.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Deployment", }) data, err := json.Marshal(u) if err != nil { t.Errorf("%v", err) } var d appsv1.Deployment if err := json.Unmarshal(data, &d); err == nil { if err := checkPodTemplate(config, d.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, d.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if test.opt.IsReplicaSetFlag { if (int)(*d.Spec.Replicas) != replicas { t.Errorf("Expected %d replicas, got %d", replicas, d.Spec.Replicas) } } else { if (int)(*d.Spec.Replicas) != newServiceConfig().Replicas { t.Errorf("Expected %d replicas, got %d", newServiceConfig().Replicas, d.Spec.Replicas) } } foundD = true } } } } if test.opt.CreateDS { if ds, ok := obj.(*appsv1.DaemonSet); ok { if err := checkPodTemplate(config, ds.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, ds.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 { t.Errorf("Expect selector be set, got: %#v", ds.Spec.Selector) } foundDS = true } if u, ok := obj.(*unstructured.Unstructured); ok { if u.GetKind() == "DaemonSet" { u.SetGroupVersionKind(schema.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "DaemonSet", }) data, err := json.Marshal(u) if err != nil { t.Errorf("%v", err) } var ds appsv1.DaemonSet if err := json.Unmarshal(data, &ds); err == nil { if err := checkPodTemplate(config, ds.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, ds.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } foundDS = true } } } } if test.opt.Controller == StatefulStateController { if ss, ok := obj.(*appsv1.StatefulSet); ok { if err := checkPodTemplate(config, ss.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, ss.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if test.opt.IsReplicaSetFlag { if (int)(*ss.Spec.Replicas) != replicas { t.Errorf("Expected %d replicas, got %d", replicas, ss.Spec.Replicas) } } else { if (int)(*ss.Spec.Replicas) != newServiceConfig().Replicas { t.Errorf("Expected %d replicas, got %d", newServiceConfig().Replicas, ss.Spec.Replicas) } } foundSS = true } if u, ok := obj.(*unstructured.Unstructured); ok { if u.GetKind() == "Statefulset" { u.SetGroupVersionKind(schema.GroupVersionKind{ Group: "apps", Version: "v1", Kind: "Statefulset", }) data, err := json.Marshal(u) if err != nil { t.Errorf("%v", err) } var d appsv1.Deployment if err := json.Unmarshal(data, &d); err == nil { if err := checkPodTemplate(config, d.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, d.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if test.opt.IsReplicaSetFlag { if (int)(*d.Spec.Replicas) != replicas { t.Errorf("Expected %d replicas, got %d", replicas, d.Spec.Replicas) } } else { if (int)(*d.Spec.Replicas) != newServiceConfig().Replicas { t.Errorf("Expected %d replicas, got %d", newServiceConfig().Replicas, d.Spec.Replicas) } } foundSS = true } } } } // TODO: k8s & openshift transformer is now separated; either separate the test or combine the transformer if test.opt.CreateDeploymentConfig { if dc, ok := obj.(*deployapi.DeploymentConfig); ok { if err := checkPodTemplate(config, *dc.Spec.Template, labelsWithNetwork); err != nil { t.Errorf("%v", err) } if err := checkMeta(config, dc.ObjectMeta, name, true); err != nil { t.Errorf("%v", err) } if (int)(dc.Spec.Replicas) != replicas { t.Errorf("Expected %d replicas, got %d", replicas, dc.Spec.Replicas) } if len(dc.Spec.Selector) == 0 { t.Errorf("Expect selector be set, got: %#v", dc.Spec.Selector) } foundDC = true } } } if !foundSVC { t.Errorf("Unexpected Service not created") } if test.opt.CreateD != foundD { t.Errorf("Expected create Deployment: %v, found Deployment: %v", test.opt.CreateD, foundD) } if test.opt.CreateDS != foundDS { t.Errorf("Expected create Daemon Set: %v, found Daemon Set: %v", test.opt.CreateDS, foundDS) } if test.opt.Controller == StatefulStateController && !foundSS { t.Errorf("Expected create StatefulStateController") } if test.opt.CreateDeploymentConfig != foundDC { t.Errorf("Expected create Deployment Config: %v, found Deployment Config: %v", test.opt.CreateDeploymentConfig, foundDC) } } } func TestConvertRestartOptions(t *testing.T) { var opt kobject.ConvertOptions var k Kubernetes testCases := map[string]struct { svc kobject.KomposeObject restartPolicy api.RestartPolicy }{ "'restart' is set to 'no'": {kobject.KomposeObject{ServiceConfigs: map[string]kobject.ServiceConfig{"app": {Image: "foobar", Restart: "no"}}}, api.RestartPolicyNever}, "'restart' is set to 'on-failure'": {kobject.KomposeObject{ServiceConfigs: map[string]kobject.ServiceConfig{"app": {Image: "foobar", Restart: "on-failure"}}}, api.RestartPolicyOnFailure}, } for name, test := range testCases { t.Log("Test Case:", name) objs, err := k.Transform(test.svc, opt) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } if len(objs) != 1 { t.Errorf("Expected only one pod, more elements generated.") } for _, obj := range objs { if pod, ok := obj.(*api.Pod); ok { if pod.Spec.RestartPolicy != test.restartPolicy { t.Errorf("Expected restartPolicy as %s, got %#v", test.restartPolicy, pod.Spec.RestartPolicy) } } else { t.Errorf("Expected 'pod' object not found one") } } } } func TestRestartOnFailure(t *testing.T) { kobjectWithRestartOnFailure := newKomposeObject() serviceConfig := kobjectWithRestartOnFailure.ServiceConfigs["app"] serviceConfig.Restart = "on-failure" kobjectWithRestartOnFailure.ServiceConfigs = map[string]kobject.ServiceConfig{"app": serviceConfig} // define all test cases for RestartOnFailure function replicas := 2 testCase := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions }{ // objects generated are deployment, service and replication controller "Do not Create Deployment (D) with restart:'on-failure'": {kobjectWithRestartOnFailure, kobject.ConvertOptions{IsDeploymentFlag: true, Replicas: replicas}}, "Do not Create DaemonSet (DS) with restart:'on-failure'": {kobjectWithRestartOnFailure, kobject.ConvertOptions{IsDaemonSetFlag: true, Replicas: replicas}}, "Do not Create ReplicationController (RC) with restart:'on-failure'": {kobjectWithRestartOnFailure, kobject.ConvertOptions{IsReplicationControllerFlag: true, Replicas: replicas}}, } for name, test := range testCase { t.Log("Test case:", name) k := Kubernetes{} _, err := k.Transform(test.komposeObject, test.opt) if err != nil { t.Errorf("Expected nil error, got %v instead", err) } } } func TestInitPodSpec(t *testing.T) { name := "foo" k := Kubernetes{} result := k.InitPodSpec(name, newServiceConfig().Image, "") if result.Containers[0].Name != "foo" && result.Containers[0].Image != "image" { t.Fatalf("Pod object not found") } } func TestConfigTmpfs(t *testing.T) { name := "foo" k := Kubernetes{} resultVolumeMount, resultVolume := k.ConfigTmpfs(name, newServiceConfig()) if resultVolumeMount[0].Name != "foo-tmpfs0" || resultVolume[0].EmptyDir.Medium != "Memory" { t.Fatalf("Tmpfs not found") } } func TestConfigCapabilities(t *testing.T) { testCases := map[string]struct { service kobject.ServiceConfig result api.Capabilities }{ "ConfigCapsWithAddDrop": {kobject.ServiceConfig{CapAdd: []string{"CHOWN", "SETUID"}, CapDrop: []string{"KILL"}}, api.Capabilities{Add: []api.Capability{api.Capability("CHOWN"), api.Capability("SETUID")}, Drop: []api.Capability{api.Capability("KILL")}}}, "ConfigCapsNoAddDrop": {kobject.ServiceConfig{CapAdd: nil, CapDrop: nil}, api.Capabilities{Add: []api.Capability{}, Drop: []api.Capability{}}}, } for name, test := range testCases { t.Log("Test case:", name) result := ConfigCapabilities(test.service) if !reflect.DeepEqual(result.Add, test.result.Add) || !reflect.DeepEqual(result.Drop, test.result.Drop) { t.Errorf("Not expected result for ConfigCapabilities") } } } func TestConfigAffinity(t *testing.T) { testCases := map[string]struct { service kobject.ServiceConfig result *api.Affinity }{ "ConfigAffinity": { service: kobject.ServiceConfig{ Placement: kobject.Placement{ PositiveConstraints: map[string]string{ "foo": "bar", }, NegativeConstraints: map[string]string{ "baz": "qux", }, }, }, result: &api.Affinity{ NodeAffinity: &api.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ NodeSelectorTerms: []api.NodeSelectorTerm{ { MatchExpressions: []api.NodeSelectorRequirement{ {Key: "foo", Operator: api.NodeSelectorOpIn, Values: []string{"bar"}}, {Key: "baz", Operator: api.NodeSelectorOpNotIn, Values: []string{"qux"}}, }, }, }, }, }, }, }, "ConfigAffinity (nil)": { kobject.ServiceConfig{}, nil, }, } for name, test := range testCases { t.Log("Test case:", name) result := ConfigAffinity(test.service) if !reflect.DeepEqual(result, test.result) { t.Errorf("Not expected result for ConfigAffinity") } } } func TestConfigTopologySpreadConstraints(t *testing.T) { serviceName := "app" testCases := map[string]struct { service kobject.ServiceConfig result []api.TopologySpreadConstraint }{ "ConfigTopologySpreadConstraint": { service: kobject.ServiceConfig{ Name: serviceName, Placement: kobject.Placement{ Preferences: []string{ "zone", "ssd", }, }, }, result: []api.TopologySpreadConstraint{ { MaxSkew: 2, TopologyKey: "zone", WhenUnsatisfiable: api.ScheduleAnyway, LabelSelector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(serviceName), }, }, { MaxSkew: 1, TopologyKey: "ssd", WhenUnsatisfiable: api.ScheduleAnyway, LabelSelector: &metav1.LabelSelector{ MatchLabels: transformer.ConfigLabels(serviceName), }, }, }, }, } for name, test := range testCases { t.Log("Test case:", name) result := ConfigTopologySpreadConstraints(test.service) if !reflect.DeepEqual(result, test.result) { t.Errorf("Not expected result for ConfigTopologySpreadConstraints") } } } func TestMultipleContainersInPod(t *testing.T) { groupName := "pod_group" createConfig := func(name string, containerName string) kobject.ServiceConfig { config := newSimpleServiceConfig() config.Labels = map[string]string{compose.LabelServiceGroup: groupName} config.Name = name if containerName != "" { config.ContainerName = containerName } config.Volumes = []kobject.Volumes{ { VolumeName: "mountVolume", MountPath: "/data-dir", }, } return config } testCases := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions expectedNumObjs int expectedNames []string }{ "Converted multiple containers to Deployments (D)": { kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{ "app1": createConfig("app1", "app1"), "app2": createConfig("app2", "app2"), }, }, kobject.ConvertOptions{ServiceGroupMode: "label", CreateD: true}, 2, []string{"app1", "app2"}}, } for name, test := range testCases { t.Log("Test case:", name) k := Kubernetes{} // Run Transform objs, err := k.Transform(test.komposeObject, test.opt) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } if len(objs) != test.expectedNumObjs { t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs)) } // Check results for _, obj := range objs { if svc, ok := obj.(*api.Service); ok { if svc.Name != groupName { t.Errorf("Expected %v returned, got %v", groupName, svc.Name) } } if deployment, ok := obj.(*appsv1.Deployment); ok { if deployment.Name != groupName { t.Errorf("Expected %v returned, got %v", groupName, deployment.Name) } if len(deployment.Spec.Template.Spec.Containers) != 2 { t.Errorf("Expected %d returned, got %d", 2, len(deployment.Spec.Template.Spec.Containers)) } nameSet := make(map[string]api.Container) for _, container := range deployment.Spec.Template.Spec.Containers { nameSet[container.Name] = container } if container, ok := nameSet[test.expectedNames[0]]; !ok { t.Errorf("Expected %v returned, got %v", test.expectedNames[0], container.Name) } else if len(container.VolumeMounts) != 1 { t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts)) } if container, ok := nameSet[test.expectedNames[1]]; !ok { t.Errorf("Expected %v returned, got %v", test.expectedNames[1], container.Name) } else if len(container.VolumeMounts) != 1 { t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts)) } } } } } func TestServiceAccountNameOnMultipleContainers(t *testing.T) { groupName := "pod_group" serviceAccountName := "my-service" createConfigs := func(labels map[string]string) map[string]kobject.ServiceConfig { createConfig := func(name string) kobject.ServiceConfig { config := newSimpleServiceConfig() config.Labels = map[string]string{compose.LabelServiceGroup: groupName} for k, v := range labels { config.Labels[k] = v } config.Name = name config.ContainerName = "" config.Volumes = []kobject.Volumes{ { VolumeName: "mountVolume", MountPath: "/data", }, } return config } return map[string]kobject.ServiceConfig{"app1": createConfig("app1"), "app2": createConfig("app2")} } testCases := map[string]struct { komposeObject kobject.KomposeObject expectedLabelNames []string }{ "Converted multiple containers with ServiceAccountName": { kobject.KomposeObject{ ServiceConfigs: createConfigs(map[string]string{compose.LabelServiceAccountName: serviceAccountName}), }, []string{serviceAccountName}}, } for name, test := range testCases { t.Log("Test case:", name) k := Kubernetes{} // Run Transform objs, err := k.Transform(test.komposeObject, kobject.ConvertOptions{ServiceGroupMode: "label", CreateD: true}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Check results for _, obj := range objs { if deployment, ok := obj.(*appsv1.Deployment); ok { if deployment.Name != groupName { t.Errorf("Expected %v returned, got %v", groupName, deployment.Name) } if deployment.Spec.Template.Spec.ServiceAccountName != serviceAccountName { t.Errorf("Expected %v returned, got %v", serviceAccountName, deployment.Spec.Template.Spec.ServiceAccountName) } } } } } func TestHealthCheckOnMultipleContainers(t *testing.T) { groupName := "pod_group" createHealthCheck := func(TCPPort int32) kobject.HealthCheck { return kobject.HealthCheck{ TCPPort: TCPPort, } } createConfig := func(name string, livenessTCPPort, readinessTCPPort int32) kobject.ServiceConfig { config := newSimpleServiceConfig() config.Labels = map[string]string{compose.LabelServiceGroup: groupName} config.Name = name config.ContainerName = name config.HealthChecks.Liveness = createHealthCheck(livenessTCPPort) config.HealthChecks.Readiness = createHealthCheck(readinessTCPPort) return config } testCases := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions expectedContainers map[string]api.Container }{ "Converted multiple containers to Deployments": { kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{ "app1": createConfig("app1", 8081, 9091), "app2": createConfig("app2", 8082, 9092), }, }, kobject.ConvertOptions{ServiceGroupMode: "label", CreateD: true}, map[string]api.Container{ "app1": { LivenessProbe: configProbe(createHealthCheck(8081)), ReadinessProbe: configProbe(createHealthCheck(9091)), }, "app2": { LivenessProbe: configProbe(createHealthCheck(8082)), ReadinessProbe: configProbe(createHealthCheck(9092)), }, }, }, } for name, test := range testCases { t.Log("Test case:", name) k := Kubernetes{} // Run Transform objs, err := k.Transform(test.komposeObject, test.opt) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } // Check results for _, obj := range objs { if deployment, ok := obj.(*appsv1.Deployment); ok { if len(deployment.Spec.Template.Spec.Containers) != len(test.expectedContainers) { t.Errorf("Containers len is not equal, expected %d, got %d", len(deployment.Spec.Template.Spec.Containers), len(test.expectedContainers)) } for _, result := range deployment.Spec.Template.Spec.Containers { expected, ok := test.expectedContainers[result.Name] if !ok { t.Errorf("Container %s doesn't expected", result.Name) } if !reflect.DeepEqual(result.LivenessProbe, expected.LivenessProbe) { t.Errorf("Container %s: LivenessProbe expected %v returned, got %v", result.Name, expected.LivenessProbe, result.LivenessProbe) } if !reflect.DeepEqual(result.ReadinessProbe, expected.ReadinessProbe) { t.Errorf("Container %s: ReadinessProbe expected %v returned, got %v", result.Name, expected.ReadinessProbe, result.ReadinessProbe) } } } } } } func TestCreatePVC(t *testing.T) { storageClassName := "custom-storage-class-name" k := Kubernetes{} result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName) if err != nil { t.Error(errors.Wrap(err, "k.CreatePVC failed")) } if *result.Spec.StorageClassName != storageClassName { t.Errorf("Expected %s returned, got %s", storageClassName, *result.Spec.StorageClassName) } } func TestCreateHostPortAndProtocol(t *testing.T) { groupName := "pod_group" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newKomposeObjectHostPortProtocolConfig()}, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if deployment, ok := obj.(*appsv1.Deployment); ok { container := deployment.Spec.Template.Spec.Containers[0] port := container.Ports[0] containerPort := port.ContainerPort hostPort := port.HostPort protocol := port.Protocol expectedPort := komposeObject.ServiceConfigs["app"].Port[0] expectedContainerPort := expectedPort.ContainerPort expectedHostPort := expectedPort.HostPort expectedProtocol := expectedPort.Protocol if containerPort != expectedContainerPort { t.Errorf("Expected container port %v, got %v", expectedContainerPort, containerPort) } if hostPort != expectedHostPort { t.Errorf("Expected host port %v, got %v", expectedHostPort, hostPort) } if protocol != api.Protocol(expectedProtocol) { t.Errorf("Expected protocol %v, got %v", expectedProtocol, protocol) } } } } func TestServiceExternalTrafficPolicy(t *testing.T) { groupName := "pod_group" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()}, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if service, ok := obj.(*api.Service); ok { serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy) if serviceExternalTrafficPolicy != strings.ToLower(string(api.ServiceExternalTrafficPolicyTypeLocal)) { t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy) } serviceType := service.Spec.Type if serviceType != api.ServiceTypeLoadBalancer { t.Errorf("Expected LoadBalancer as service type, got %v", serviceType) } } } } func TestVolumeMountSubPath(t *testing.T) { groupName := "pod_group" expectedSubPathValue := "test-subpath" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithServiceVolumeMount(expectedSubPathValue)}, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if deployment, ok := obj.(*appsv1.Deployment); ok { volMountSubPath := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath if volMountSubPath != expectedSubPathValue { t.Errorf("Expected VolumeMount Subpath %v, got %v", expectedSubPathValue, volMountSubPath) } } } } func TestNetworkPoliciesGeneration(t *testing.T) { groupName := "pod_group" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName, GenerateNetworkPolicies: true}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if np, ok := obj.(*networkingv1.NetworkPolicy); ok { matchLabelsLength := len(np.Spec.PodSelector.MatchLabels) if matchLabelsLength == 0 { t.Errorf("Expected length of Network Policy PodSelector to be greater than 0, got %v", matchLabelsLength) } } } } func TestServiceGroupModeImagePullSecrets(t *testing.T) { groupName := "pod_group" serviceConfig := newServiceConfig() komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": serviceConfig}, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName, GenerateNetworkPolicies: true}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } expectedSecretsLen := len(serviceConfig.ImagePullSecret) for _, obj := range objs { if deployment, ok := obj.(*appsv1.Deployment); ok { secretsLen := len(deployment.Spec.Template.Spec.ImagePullSecrets) if secretsLen != expectedSecretsLen { t.Errorf("Expected length of Deployment ImagePullSecrets to be equal to %v, got %v", expectedSecretsLen, secretsLen) } } } } func TestNamespaceGeneration(t *testing.T) { ns := "app" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, Namespace: ns, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if namespace, ok := obj.(*api.Namespace); ok { if strings.ToLower(ns) != strings.ToLower(namespace.ObjectMeta.Name) { t.Errorf("Expected namespace name %v, got %v", ns, namespace.ObjectMeta.Name) } } if dep, ok := obj.(*appsv1.Deployment); ok { if dep.ObjectMeta.Namespace != ns { t.Errorf("Expected deployment namespace %v, got %v", ns, dep.ObjectMeta.Namespace) } } } } // Test namespace generation with namespace being blank / "" func TestNamespaceGenerationBlank(t *testing.T) { ns := "" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, Namespace: ns, } k := Kubernetes{} objs, err := k.Transform(komposeObject, kobject.ConvertOptions{}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if namespace, ok := obj.(*api.Namespace); ok { if strings.ToLower(ns) != strings.ToLower(namespace.ObjectMeta.Name) { t.Errorf("Expected namespace name %v, got %v", ns, namespace.ObjectMeta.Name) } } if dep, ok := obj.(*appsv1.Deployment); ok { if dep.ObjectMeta.Namespace != ns { t.Errorf("Expected deployment namespace %v, got %v", ns, dep.ObjectMeta.Namespace) } } } } func TestKubernetes_CreateSecrets(t *testing.T) { var komposeDefaultObject []kobject.KomposeObject dataSecrets := []SecretsConfig{ { nameSecretConfig: "config-ini", nameSecret: "debug-config-ini", pathFile: "../../../docs/CNAME", }, { nameSecretConfig: "new-config-init", nameSecret: "new-debug-config-ini", pathFile: "../../../docs/CNAME", }, } for i := 0; i < len(dataSecrets); i++ { komposeDefaultObject = append(komposeDefaultObject, newKomposeObject()) komposeDefaultObject[i].Secrets = newSecrets(dataSecrets[i]) } type fields struct { Opt kobject.ConvertOptions } type args struct { komposeObject kobject.KomposeObject } tests := []struct { name string fields fields args args want []*api.Secret wantErr bool }{ { name: "CreateSecrets from default KomposeObject and secrets taken from CNAME file", args: args{ komposeObject: komposeDefaultObject[0], }, want: []*api.Secret{ { TypeMeta: metav1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: FormatResourceName(dataSecrets[0].nameSecretConfig), Labels: transformer.ConfigLabels(dataSecrets[0].nameSecretConfig), }, Type: api.SecretTypeOpaque, Data: map[string][]byte{dataSecrets[0].nameSecretConfig: []byte("kompose.io")}, }, }, }, { name: "CreateSecrets from default KomposeObject and secrets taken from CNAME file", args: args{ komposeObject: komposeDefaultObject[1], }, want: []*api.Secret{ { TypeMeta: metav1.TypeMeta{ Kind: "Secret", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: FormatResourceName(dataSecrets[1].nameSecretConfig), Labels: transformer.ConfigLabels(dataSecrets[1].nameSecretConfig), }, Type: api.SecretTypeOpaque, Data: map[string][]byte{dataSecrets[1].nameSecretConfig: []byte("kompose.io")}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &Kubernetes{ Opt: tt.fields.Opt, } got, err := k.CreateSecrets(tt.args.komposeObject) if (err != nil) != tt.wantErr { t.Errorf("Kubernetes.CreateSecrets() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Kubernetes.CreateSecrets() = %v, want %v", got, tt.want) } }) } } // struct defines the configuration parameters required for creating a secret type SecretsConfig struct { nameSecretConfig string nameSecret string pathFile string } // creates a new instance of types.Secrets based on the provided SecretsConfig parameter func newSecrets(stringsSecretConfig SecretsConfig) types.Secrets { return types.Secrets{ stringsSecretConfig.nameSecretConfig: types.SecretConfig{ Name: stringsSecretConfig.nameSecret, File: stringsSecretConfig.pathFile, }, } } // TestPargeEnvFiletoConfigMaps tests the conversion of environment variable files to ConfigMap objects func TestPargeEnvFiletoConfigMaps(t *testing.T) { // Prepare a temp .env file for the expression test tempFile, err := os.CreateTemp("", ".env") if err != nil { t.Fatalf("Failed to create temp env file: %v", err) } defer os.Remove(tempFile.Name()) content := []byte(`FOO=bar BAR=${FOO}_baz DOC_ENGINE=${DOC_ENGINE:-elasticsearch} COMPOSE_PROFILES=${DOC_ENGINE} UNDEFINED_VAR=${MISSING_VAR:-default_value} `) if _, err := tempFile.Write(content); err != nil { t.Fatalf("Failed to write to temp env file: %v", err) } tempFile.Close() tempFileName := filepath.Base(tempFile.Name()) testCases := map[string]struct { service kobject.ServiceConfig opt kobject.ConvertOptions want int check func(t *testing.T, cms []runtime.Object) }{ "Env file with variable expressions": { service: kobject.ServiceConfig{ Name: "test-app", Environment: []kobject.EnvVar{ { Name: "DOC_ENGINE", Value: "test-env", }, }, EnvFile: []string{tempFileName}, }, opt: kobject.ConvertOptions{InputFiles: []string{tempFile.Name()}}, want: 1, check: func(t *testing.T, cms []runtime.Object) { cm, ok := cms[0].(*api.ConfigMap) if !ok { t.Errorf("Returned object is not a ConfigMap") return } if cm.Data["FOO"] != "bar" { t.Errorf("Expected FOO=bar, got %s", cm.Data["FOO"]) } if cm.Data["BAR"] != "bar_baz" { t.Errorf("Expected BAR=bar_baz, got %s", cm.Data["BAR"]) } if cm.Data["DOC_ENGINE"] != "test-env" { t.Errorf("Expected DOC_ENGINE=test-env, got %s", cm.Data["DOC_ENGINE"]) } if cm.Data["COMPOSE_PROFILES"] != "test-env" { t.Errorf("Expected COMPOSE_PROFILES=test-env, got %s", cm.Data["COMPOSE_PROFILES"]) } if cm.Data["UNDEFINED_VAR"] != "default_value" { t.Errorf("Expected UNDEFINED_VAR=default_value, got %s", cm.Data["UNDEFINED_VAR"]) } }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { k := Kubernetes{} cms := k.PargeEnvFiletoConfigMaps(tc.service.Name, tc.service, tc.opt) if len(cms) != tc.want { t.Errorf("Expected %d ConfigMaps, got %d", tc.want, len(cms)) } tc.check(t, cms) }) } } ================================================ FILE: pkg/transformer/kubernetes/podspec.go ================================================ package kubernetes import ( "reflect" "strconv" "strings" mapset "github.com/deckarep/golang-set" "github.com/kubernetes/kompose/pkg/kobject" "github.com/pkg/errors" log "github.com/sirupsen/logrus" api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" ) // PodSpec holds the spec of k8s pod. type PodSpec struct { api.PodSpec } // PodSpecOption holds the function to apply on a PodSpec type PodSpecOption func(*PodSpec) // AddContainer method is responsible for adding a new container to a k8s Pod. func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption { return func(podSpec *PodSpec) { name := GetContainerName(service) image := service.Image if image == "" { image = name } envs, envsFrom, err := ConfigEnvs(service, opt) if err != nil { panic("Unable to load env variables") } podSpec.Containers = append(podSpec.Containers, api.Container{ Name: name, Image: image, Env: envs, EnvFrom: envsFrom, Command: service.Command, Args: service.Args, WorkingDir: service.WorkingDir, Stdin: service.Stdin, TTY: service.Tty, LivenessProbe: configProbe(service.HealthChecks.Liveness), ReadinessProbe: configProbe(service.HealthChecks.Readiness), }) if service.ImagePullSecret != "" { podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, api.LocalObjectReference{ Name: service.ImagePullSecret, }) } podSpec.Affinity = ConfigAffinity(service) } } // TerminationGracePeriodSeconds method is responsible for attributing the grace period seconds option to a pod func TerminationGracePeriodSeconds(name string, service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { var err error if service.StopGracePeriod != "" { podSpec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod) if err != nil { log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name) } } } } // ResourcesLimits Configure the resource limits func ResourcesLimits(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { if service.MemLimit != 0 || service.CPULimit != 0 { resourceLimit := api.ResourceList{} if service.MemLimit != 0 { resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat") } if service.CPULimit != 0 { resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI) } for i := range podSpec.Containers { podSpec.Containers[i].Resources.Limits = resourceLimit } } } } // ResourcesRequests Configure the resource requests func ResourcesRequests(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { if service.MemReservation != 0 || service.CPUReservation != 0 { resourceRequests := api.ResourceList{} if service.MemReservation != 0 { resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat") } if service.CPUReservation != 0 { resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI) } for i := range podSpec.Containers { podSpec.Containers[i].Resources.Requests = resourceRequests } } } } // SecurityContext Configure SecurityContext func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { // Configure resource reservations podSecurityContext := &api.PodSecurityContext{} //set pid namespace mode if service.Pid != "" { if service.Pid == "host" { // podSecurityContext.HostPID = true } else { log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid) } } //set supplementalGroups if service.GroupAdd != nil { podSecurityContext.SupplementalGroups = service.GroupAdd } //set Pod FsGroup if service.FsGroup != 0 { podSecurityContext.FSGroup = &service.FsGroup } // Setup security context securityContext := &api.SecurityContext{} if service.Privileged { securityContext.Privileged = &service.Privileged } if service.User != "" { switch userparts := strings.Split(service.User, ":"); len(userparts) { default: log.Warn("Ignoring ill-formed user directive. Must be in format UID or UID:GID.") case 1: uid, err := strconv.ParseInt(userparts[0], 10, 64) if err != nil { log.Warn("Ignoring user directive. User to be specified as a UID (numeric).") } else { securityContext.RunAsUser = &uid } case 2: uid, err := strconv.ParseInt(userparts[0], 10, 64) if err != nil { log.Warn("Ignoring user name in user directive. User to be specified as a UID (numeric).") } else { securityContext.RunAsUser = &uid } gid, err := strconv.ParseInt(userparts[1], 10, 64) if err != nil { log.Warn("Ignoring group name in user directive. Group to be specified as a GID (numeric).") } else { securityContext.RunAsGroup = &gid } } } // Configure capabilities capabilities := ConfigCapabilities(service) //set capabilities if it is not empty if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 { securityContext.Capabilities = capabilities } // update template only if securityContext is not empty if *securityContext != (api.SecurityContext{}) { // select the correct container to update by name for i := range podSpec.Containers { if podSpec.Containers[i].Name == GetContainerName(service) { podSpec.Containers[i].SecurityContext = securityContext } } } if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) { podSpec.SecurityContext = podSecurityContext } } } // SetVolumeNames method return a set of volume names func SetVolumeNames(volumes []api.Volume) mapset.Set { set := mapset.NewSet() for _, volume := range volumes { set.Add(volume.Name) } return set } // SetVolumes method returns a method that adds the volumes to the pod spec func SetVolumes(volumes []api.Volume) PodSpecOption { return func(podSpec *PodSpec) { volumesSet := SetVolumeNames(volumes) containerVolumesSet := SetVolumeNames(podSpec.Volumes) for diffVolumeName := range volumesSet.Difference(containerVolumesSet).Iter() { for _, volume := range volumes { if volume.Name == diffVolumeName { podSpec.Volumes = append(podSpec.Volumes, volume) break } } } } } // SetVolumeMountPaths method returns a set of volumes mount path func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set { set := mapset.NewSet() for _, volumeMount := range volumesMount { set.Add(volumeMount.MountPath) } return set } // SetVolumeMounts returns a function which adds the volume mounts option to the pod spec func SetVolumeMounts(volumesMount []api.VolumeMount) PodSpecOption { return func(podSpec *PodSpec) { volumesMountSet := SetVolumeMountPaths(volumesMount) for i := range podSpec.Containers { containerVolumeMountsSet := SetVolumeMountPaths(podSpec.Containers[i].VolumeMounts) for diffVolumeMountPath := range volumesMountSet.Difference(containerVolumeMountsSet).Iter() { for _, volumeMount := range volumesMount { if volumeMount.MountPath == diffVolumeMountPath { podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, volumeMount) break } } } } } } // SetPorts Configure ports func SetPorts(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { // Configure the container ports. ports := ConfigPorts(service) for i := range podSpec.Containers { if GetContainerName(service) == podSpec.Containers[i].Name { podSpec.Containers[i].Ports = ports } } } } // ImagePullPolicy Configure the image pull policy func ImagePullPolicy(name string, service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { if policy, err := GetImagePullPolicy(name, service.ImagePullPolicy); err != nil { panic(err) } else { for i := range podSpec.Containers { podSpec.Containers[i].ImagePullPolicy = policy } } } } // RestartPolicy Configure the container restart policy. func RestartPolicy(name string, service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { if restart, err := GetRestartPolicy(name, service.Restart); err != nil { panic(err) } else { podSpec.RestartPolicy = restart } } } // HostName configure the host name of a pod func HostName(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { // Configure hostname/domain_name settings if service.HostName != "" { podSpec.Hostname = service.HostName } } } // DomainName configure the domain name of a pod func DomainName(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { if service.DomainName != "" { podSpec.Subdomain = service.DomainName } } } func configProbe(healthCheck kobject.HealthCheck) *api.Probe { probe := api.Probe{} // We check to see if it's blank or disable if reflect.DeepEqual(healthCheck, kobject.HealthCheck{}) || healthCheck.Disable { return nil } if len(healthCheck.Test) > 0 { probe.ProbeHandler = api.ProbeHandler{ Exec: &api.ExecAction{ Command: healthCheck.Test, }, } } else if !reflect.ValueOf(healthCheck.HTTPPath).IsZero() && !reflect.ValueOf(healthCheck.HTTPPort).IsZero() { probe.ProbeHandler = api.ProbeHandler{ HTTPGet: &api.HTTPGetAction{ Path: healthCheck.HTTPPath, Port: intstr.FromInt(int(healthCheck.HTTPPort)), }, } } else if !reflect.ValueOf(healthCheck.TCPPort).IsZero() { probe.ProbeHandler = api.ProbeHandler{ TCPSocket: &api.TCPSocketAction{ Port: intstr.FromInt(int(healthCheck.TCPPort)), }, } } else { panic(errors.New("Health check must contain a command")) } probe.TimeoutSeconds = healthCheck.Timeout probe.PeriodSeconds = healthCheck.Interval probe.FailureThreshold = healthCheck.Retries // See issue: https://github.com/docker/cli/issues/116 // StartPeriod has been added to v3.4 of the compose probe.InitialDelaySeconds = healthCheck.StartPeriod return &probe } // ServiceAccountName is responsible for setting the service account name to the pod spec func ServiceAccountName(serviceAccountName string) PodSpecOption { return func(podSpec *PodSpec) { podSpec.ServiceAccountName = serviceAccountName } } // TopologySpreadConstraints is responsible for setting the topology spread constraints to the pod spec func TopologySpreadConstraints(service kobject.ServiceConfig) PodSpecOption { return func(podSpec *PodSpec) { podSpec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service) } } // Append is responsible for adding the pod spec options to the particular pod func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec { for _, option := range ops { option(podSpec) } return podSpec } // Get is responsible for returning the pod spec of a particular pod func (podSpec *PodSpec) Get() api.PodSpec { return podSpec.PodSpec } ================================================ FILE: pkg/transformer/openshift/openshift.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package openshift import ( "fmt" "os" "sort" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/transformer" "github.com/kubernetes/kompose/pkg/transformer/kubernetes" deployapi "github.com/openshift/api/apps/v1" buildapi "github.com/openshift/api/build/v1" imageapi "github.com/openshift/api/image/v1" routeapi "github.com/openshift/api/route/v1" "github.com/pkg/errors" log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" kapi "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) // OpenShift implements Transformer interface and represents OpenShift transformer type OpenShift struct { // Anonymous field allows for inheritance. We are basically inheriting // all of kubernetes.Kubernetes Methods and variables here. We'll overwrite // some of those methods with our own for openshift. kubernetes.Kubernetes } // list of all unsupported keys for this transformer // Keys are names of variables in kobject struct. // this is map to make searching for keys easier // to make sure that unsupported key is not going to be reported twice // by keeping record if already saw this key in another service var unsupportedKey = map[string]bool{} // initImageStream initializes ImageStream object func (o *OpenShift) initImageStream(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *imageapi.ImageStream { if service.Image == "" { service.Image = name } // Retrieve tags and image name for mapping var importPolicy imageapi.TagImportPolicy if opt.InsecureRepository { importPolicy = imageapi.TagImportPolicy{Insecure: true} } var tags []imageapi.TagReference if service.Build != "" || opt.Build != "build-config" { tags = append(tags, imageapi.TagReference{ From: &corev1.ObjectReference{ Kind: "DockerImage", Name: service.Image, }, ImportPolicy: importPolicy, Name: GetImageTag(service.Image), }) } is := &imageapi.ImageStream{ TypeMeta: kapi.TypeMeta{ Kind: "ImageStream", APIVersion: "image.openshift.io/v1", }, ObjectMeta: kapi.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), }, Spec: imageapi.ImageStreamSpec{ Tags: tags, }, } return is } func initBuildConfig(name string, service kobject.ServiceConfig, repo string, branch string) (*buildapi.BuildConfig, error) { contextDir, err := GetAbsBuildContext(service.Build) envList := transformer.EnvSort{} for envName, envValue := range service.BuildArgs { if *envValue == "\x00" { *envValue = os.Getenv(envName) } envList = append(envList, corev1.EnvVar{Name: envName, Value: *envValue}) } // Stable sorts data while keeping the original order of equal elements // we need this because envs are not populated in any random order // this sorting ensures they are populated in a particular order sort.Stable(envList) if err != nil { return nil, errors.Wrap(err, name+"buildconfig cannot be created due to error in creating build context, getAbsBuildContext failed") } bc := &buildapi.BuildConfig{ TypeMeta: kapi.TypeMeta{ Kind: "BuildConfig", APIVersion: "v1", }, ObjectMeta: kapi.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), }, Spec: buildapi.BuildConfigSpec{ Triggers: []buildapi.BuildTriggerPolicy{ {Type: "ConfigChange"}, }, RunPolicy: "Serial", CommonSpec: buildapi.CommonSpec{ Source: buildapi.BuildSource{ Git: &buildapi.GitBuildSource{ Ref: branch, URI: repo, }, ContextDir: contextDir, }, Strategy: buildapi.BuildStrategy{ DockerStrategy: &buildapi.DockerBuildStrategy{ DockerfilePath: service.Dockerfile, Env: envList, }, }, Output: buildapi.BuildOutput{ To: &corev1.ObjectReference{ Kind: "ImageStreamTag", Name: name + ":" + GetImageTag(service.Image), }, }, }, }, } return bc, nil } // initDeploymentConfig initializes OpenShifts DeploymentConfig object func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { containerName := []string{name} // Properly add tags to the image name tag := GetImageTag(service.Image) // Use ContainerName if it was set if service.ContainerName != "" { containerName = []string{service.ContainerName} } var podSpec corev1.PodSpec if len(service.Configs) > 0 { podSpec = o.InitPodSpecWithConfigMap(name, " ", service) } else { podSpec = o.InitPodSpec(name, " ", "") } dc := &deployapi.DeploymentConfig{ TypeMeta: kapi.TypeMeta{ Kind: "DeploymentConfig", APIVersion: "apps.openshift.io/v1", }, ObjectMeta: kapi.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), }, Spec: deployapi.DeploymentConfigSpec{ Replicas: int32(replicas), Selector: transformer.ConfigLabels(name), //UniqueLabelKey: p.Name, Template: &corev1.PodTemplateSpec{ ObjectMeta: kapi.ObjectMeta{ Labels: transformer.ConfigLabels(name), }, Spec: podSpec, }, Triggers: []deployapi.DeploymentTriggerPolicy{ // Trigger new deploy when DeploymentConfig is created (config change) { Type: deployapi.DeploymentTriggerOnConfigChange, }, { Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{ //Automatic - if new tag is detected - update image update inside the pod template Automatic: true, ContainerNames: containerName, From: corev1.ObjectReference{ Name: name + ":" + tag, Kind: "ImageStreamTag", }, }, }, }, }, } update := service.GetOSUpdateStrategy() if update != nil { dc.Spec.Strategy = deployapi.DeploymentStrategy{ Type: deployapi.DeploymentStrategyTypeRolling, RollingParams: update, } log.Debugf("Set deployment '%s' rolling update: MaxSurge: %s, MaxUnavailable: %s", name, update.MaxSurge.String(), update.MaxUnavailable.String()) } return dc } func (o *OpenShift) initRoute(name string, service kobject.ServiceConfig, port int32) *routeapi.Route { route := &routeapi.Route{ TypeMeta: kapi.TypeMeta{ Kind: "Route", APIVersion: "v1", }, ObjectMeta: kapi.ObjectMeta{ Name: name, Labels: transformer.ConfigLabels(name), }, Spec: routeapi.RouteSpec{ Port: &routeapi.RoutePort{ TargetPort: intstr.IntOrString{ IntVal: port, }, }, To: routeapi.RouteTargetReference{ Kind: "Service", Name: name, }, }, } if service.ExposeService != "true" { route.Spec.Host = service.ExposeService } return route } // Transform maps komposeObject to openshift objects // returns objects that are already sorted in the way that Services are first func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) ([]runtime.Object, error) { noSupKeys := o.Kubernetes.CheckUnsupportedKey(&komposeObject, unsupportedKey) for _, keyName := range noSupKeys { log.Warningf("OpenShift provider doesn't support %s key - ignoring", keyName) } // this will hold all the converted data var allobjects []runtime.Object if komposeObject.Namespace != "" { ns := transformer.CreateNamespace(komposeObject.Namespace) allobjects = append(allobjects, ns) } var err error var composeFileDir string buildRepo := opt.BuildRepo buildBranch := opt.BuildBranch if komposeObject.Secrets != nil { secrets, err := o.CreateSecrets(komposeObject) if err != nil { return nil, errors.Wrapf(err, "create secrets error") } for _, item := range secrets { allobjects = append(allobjects, item) } } sortedKeys := kubernetes.SortedKeys(komposeObject.ServiceConfigs) for _, name := range sortedKeys { service := komposeObject.ServiceConfigs[name] var objects []runtime.Object //replicas var replica int if opt.IsReplicaSetFlag || service.Replicas == 0 { replica = opt.Replicas } else { replica = service.Replicas } // If Deploy.Mode = Global has been set, make replica = 1 when generating DeploymentConfig if service.DeployMode == "global" { replica = 1 } // Must build the images before conversion (got to add service.Image in case 'image' key isn't provided // Check to see if there is an InputFile (required!) before we build the container // Check that there's actually a Build key // Lastly, we must have an Image name to continue if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" { // If there's no "image" key, use the name of the container that's built if service.Image == "" { service.Image = name } if service.Image == "" { return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name) } // Build the container! err := transformer.BuildDockerImage(service, name) if err != nil { log.Fatalf("Unable to build Docker container for service %v: %v", name, err) } // Push the built container to the repo! err = transformer.PushDockerImageWithOpt(service, name, opt) if err != nil { log.Fatalf("Unable to push Docker image for service %v: %v", name, err) } } // Generate pod or cronjob and configmap objects if service.Restart == "no" || service.Restart == "on-failure" { // Error out if Controller Object is specified with restart: 'on-failure' if opt.IsDeploymentConfigFlag { return nil, errors.New("Controller object cannot be specified with restart: 'on-failure'") } if service.CronJobSchedule != "" { cronJob := o.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit) objects = append(objects, cronJob) } else { pod := o.InitPod(name, service) objects = append(objects, pod) } envConfigMaps := o.PargeEnvFiletoConfigMaps(name, service, opt) objects = append(objects, envConfigMaps...) } else { objects = o.CreateWorkloadAndConfigMapObjects(name, service, opt) if opt.CreateDeploymentConfig { objects = append(objects, o.initDeploymentConfig(name, service, replica)) // OpenShift DeploymentConfigs // create ImageStream after deployment (creating IS will trigger new deployment) objects = append(objects, o.initImageStream(name, service, opt)) } // buildconfig needs to be added to objects after imagestream because of this Openshift bug: https://github.com/openshift/origin/issues/4518 // Generate BuildConfig if the parameter has been passed if service.Build != "" && opt.Build == "build-config" { // Get the compose file directory composeFileDir, err = transformer.GetComposeFileDir(opt.InputFiles) if err != nil { log.Warningf("Error %v in detecting compose file's directory.", err) continue } // Check for Git if !HasGitBinary() && (buildRepo == "" || buildBranch == "") { return nil, errors.New("Git is not installed! Please install Git to create buildconfig, else supply source repository and branch to use for build using '--build-repo', '--build-branch' options respectively") } // Check the Git branch if buildBranch == "" { buildBranch, err = GetGitCurrentBranch(composeFileDir) if err != nil { return nil, errors.Wrap(err, "Buildconfig cannot be created because current git branch couldn't be detected.") } } // Detect the remote branches if opt.BuildRepo == "" { if err != nil { return nil, errors.Wrap(err, "Buildconfig cannot be created because remote for current git branch couldn't be detected.") } buildRepo, err = GetGitCurrentRemoteURL(composeFileDir) if err != nil { return nil, errors.Wrap(err, "Buildconfig cannot be created because git remote origin repo couldn't be detected.") } } // Initialize and build BuildConfig bc, err := initBuildConfig(name, service, buildRepo, buildBranch) if err != nil { return nil, errors.Wrap(err, "initBuildConfig failed") } objects = append(objects, bc) // Openshift BuildConfigs // Log what we're doing log.Infof("Buildconfig using %s::%s as source.", buildRepo, buildBranch) } } if o.PortsExist(service) { if service.ServiceType == "LoadBalancer" { svcs := o.CreateLBService(name, service) for _, svc := range svcs { svc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy) objects = append(objects, svc) } if len(svcs) > 1 { log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalancer type") } } else { svc := o.CreateService(name, service) objects = append(objects, svc) if service.ExposeService != "" { objects = append(objects, o.initRoute(name, service, svc.Spec.Ports[0].Port)) } if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != corev1.ServiceTypeNodePort { log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType) } } } else if service.ServiceType == "Headless" { svc := o.CreateHeadlessService(name, service) objects = append(objects, svc) if service.ServiceExternalTrafficPolicy != "" { log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name) } } err := o.UpdateKubernetesObjects(name, service, opt, &objects) if err != nil { return nil, errors.Wrap(err, "Error transforming Kubernetes objects") } allobjects = append(allobjects, objects...) } // sort all object so Services are first o.SortServicesFirst(&allobjects) o.RemoveDupObjects(&allobjects) if komposeObject.Namespace != "" { transformer.AssignNamespaceToObjects(&allobjects, komposeObject.Namespace) } // o.FixWorkloadVersion(&allobjects) return allobjects, nil } ================================================ FILE: pkg/transformer/openshift/openshift_test.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package openshift import ( "os" "path/filepath" "reflect" "strings" "testing" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/testutils" "github.com/kubernetes/kompose/pkg/transformer" "github.com/kubernetes/kompose/pkg/transformer/kubernetes" deployapi "github.com/openshift/api/apps/v1" "github.com/pkg/errors" api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) func newServiceConfig() kobject.ServiceConfig { return kobject.ServiceConfig{ ContainerName: "myfoobarname", Image: "image", Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, Command: []string{"cmd"}, WorkingDir: "dir", Args: []string{"arg1", "arg2"}, VolList: []string{"/tmp/volume"}, Network: []string{"network1", "network2"}, // not supported Labels: nil, Annotations: map[string]string{"abc": "def"}, CPUQuota: 1, // not supported CapAdd: []string{"cap_add"}, // not supported CapDrop: []string{"cap_drop"}, // not supported Expose: []string{"expose"}, // not supported Privileged: true, Restart: "always", Stdin: true, Tty: true, } } func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig { loadBalancerServiceType := string(corev1.ServiceTypeLoadBalancer) return kobject.ServiceConfig{ Name: "app", Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456}}, ServiceType: loadBalancerServiceType, ServiceExternalTrafficPolicy: "local", } } func TestOpenShiftUpdateKubernetesObjects(t *testing.T) { t.Log("Test case: Testing o.UpdateKubernetesObjects()") var object []runtime.Object o := OpenShift{} serviceConfig := newServiceConfig() opt := kobject.ConvertOptions{} object = append(object, o.initDeploymentConfig("foobar", serviceConfig, 3)) o.UpdateKubernetesObjects("foobar", serviceConfig, opt, &object) for _, obj := range object { switch tobj := obj.(type) { case *deployapi.DeploymentConfig: t.Log("> Testing if stdin is set correctly") if tobj.Spec.Template.Spec.Containers[0].Stdin != serviceConfig.Stdin { t.Errorf("Expected stdin to be %v, got %v instead", serviceConfig.Stdin, tobj.Spec.Template.Spec.Containers[0].Stdin) } t.Log("> Testing if TTY is set correctly") if tobj.Spec.Template.Spec.Containers[0].TTY != serviceConfig.Tty { t.Errorf("Expected TTY to be %v, got %v instead", serviceConfig.Tty, tobj.Spec.Template.Spec.Containers[0].TTY) } } } } func TestInitDeploymentConfig(t *testing.T) { o := OpenShift{} spec := o.initDeploymentConfig("foobar", newServiceConfig(), 1) // Check that "foobar" is used correctly as a name if spec.Spec.Template.Spec.Containers[0].Name != "foobar" { t.Errorf("Expected foobar for name, actual %s", spec.Spec.Template.Spec.Containers[0].Name) } // Check that "myfoobarname" is used correctly as a ContainerName if spec.Spec.Triggers[1].ImageChangeParams.ContainerNames[0] != "myfoobarname" { t.Errorf("Expected myfoobarname for name, actual %s", spec.Spec.Triggers[1].ImageChangeParams.ContainerNames[0]) } // Check that the APIVersion is equal to "apps.openshift.io/v1" if spec.APIVersion != "apps.openshift.io/v1" { t.Errorf("Expected 'apps.openshift.io/v1' for APIVersion, actual %s", spec.APIVersion) } } func TestKomposeConvertRoute(t *testing.T) { o := OpenShift{} name := "app" sc := newServiceConfig() sc.ExposeService = "true" var port int32 = 5555 route := o.initRoute(name, sc, port) if route.ObjectMeta.Name != name { t.Errorf("Expected %s for name, actual %s", name, route.ObjectMeta.Name) } if route.Spec.To.Name != name { t.Errorf("Expected %s for name, actual %s", name, route.Spec.To.Name) } if route.Spec.Port.TargetPort.IntVal != port { t.Errorf("Expected %d for port, actual %d", port, route.Spec.Port.TargetPort.IntVal) } if route.Spec.Host != "" { t.Errorf("Expected Spec.Host to not be set, got %s instead", route.Spec.Host) } sc.ExposeService = "example.com" route = o.initRoute(name, sc, port) if route.Spec.Host != sc.ExposeService { t.Errorf("Expected %s for Spec.Host, actual %s", sc.ExposeService, route.Spec.Host) } } // Test getting git remote url for a directory func TestGetGitRemote(t *testing.T) { var output string var err error gitDir := testutils.CreateLocalGitDirectory(t) testutils.SetGitRemote(t, gitDir, "newremote", "https://git.test.com/somerepo") testutils.CreateGitRemoteBranch(t, gitDir, "newbranch", "newremote") dir := testutils.CreateLocalDirectory(t) defer os.RemoveAll(gitDir) defer os.RemoveAll(dir) testCases := map[string]struct { expectError bool dir string branch string output string }{ "Get git remote for branch success": {false, gitDir, "newbranch", "https://git.test.com/somerepo.git"}, "Get git remote error in non git dir": {true, dir, "", ""}, } for name, test := range testCases { t.Log("Test case: ", name) output, err = GetGitCurrentRemoteURL(test.dir) if test.expectError { if err == nil { t.Errorf("Expected error, got success instead!") } } else { if err != nil { t.Errorf("Expected success, got error: %v", err) } if output != test.output { t.Errorf("Expected: %#v, got: %#v", test.output, output) } } } } // Test getting current git branch in a directory func TestGitGetCurrentBranch(t *testing.T) { var output string var err error gitDir := testutils.CreateLocalGitDirectory(t) testutils.SetGitRemote(t, gitDir, "newremote", "https://git.test.com/somerepo") testutils.CreateGitRemoteBranch(t, gitDir, "newbranch", "newremote") dir := testutils.CreateLocalDirectory(t) defer os.RemoveAll(gitDir) defer os.RemoveAll(dir) testCases := map[string]struct { expectError bool dir string output string }{ "Get git current branch success": {false, gitDir, "newbranch"}, "Get git current branch error": {true, dir, ""}, } for name, test := range testCases { t.Log("Test case: ", name) output, err = GetGitCurrentBranch(test.dir) if test.expectError { if err == nil { t.Error("Expected error, got success instead!") } } else { if err != nil { t.Errorf("Expected success, got error: %v", err) } if output != test.output { t.Errorf("Expected: %#v, got: %#v", test.output, output) } } } } // Test getting compose file directory path: relative to project dir or absolute path func TestGetComposeFileDir(t *testing.T) { var output string var err error wd, _ := os.Getwd() testCases := map[string]struct { inputFiles []string output string }{ "Get compose file dir for relative input file path": {[]string{"foo/bar.yaml"}, filepath.Join(wd, "foo")}, "Get compose file dir for abs input file path": {[]string{"/abs/path/to/compose.yaml"}, "/abs/path/to"}, } for name, test := range testCases { t.Log("Test case: ", name) output, err = transformer.GetComposeFileDir(test.inputFiles) if err != nil { t.Errorf("Expected success, got error: %#v", err) } if output != test.output { t.Errorf("Expected output: %#v, got: %#v", test.output, output) } } } // Test getting build context relative to project's root dir func TestGetAbsBuildContext(t *testing.T) { var output string var err error gitDir := testutils.CreateLocalGitDirectory(t) testutils.SetGitRemote(t, gitDir, "newremote", "https://git.test.com/somerepo") testutils.CreateGitRemoteBranch(t, gitDir, "newbranch", "newremote") testutils.CreateSubdir(t, gitDir, "a/b/build") testutils.CreateSubdir(t, gitDir, "build") dir := testutils.CreateLocalDirectory(t) defer os.RemoveAll(gitDir) defer os.RemoveAll(dir) testCases := map[string]struct { expectError bool context string output string }{ "Get abs build context success case-1": {false, filepath.Join(gitDir, "a/b/build"), "a/b/build/"}, "Get abs build context success case-2": {false, filepath.Join(gitDir, "build"), "build/"}, "Get abs build context error case-1": {true, "example/build", "example/build/"}, "Get abs build context error case-2": {true, "/tmp", ""}, } for name, test := range testCases { t.Log("Test case: ", name) output, err = GetAbsBuildContext(test.context) if test.expectError { if err == nil { t.Errorf("Expected error, got success instead!") } } else { if err != nil { t.Errorf("Expected success, got error: %v", err) } if output != test.output { t.Errorf("Expected: %#v, got: %#v", test.output, output) } } } } // Test initializing buildconfig for a service func TestInitBuildConfig(t *testing.T) { serviceName := "serviceA" repo := "https://git.test.com/org/repo1" branch := "somebranch" buildArgs := []corev1.EnvVar{{Name: "name", Value: "value"}} value := "value" testDir := "a/build" dir := testutils.CreateLocalGitDirectory(t) testutils.CreateSubdir(t, dir, testDir) defer os.RemoveAll(dir) testCases := []struct { Name string ServiceConfig kobject.ServiceConfig }{ { Name: "Service config without image key", ServiceConfig: kobject.ServiceConfig{ Build: filepath.Join(dir, testDir), Dockerfile: "Dockerfile-alternate", BuildArgs: map[string]*string{"name": &value}, }, }, { Name: "Service config with image key", ServiceConfig: kobject.ServiceConfig{ Build: filepath.Join(dir, testDir), Dockerfile: "Dockerfile-alternate", BuildArgs: map[string]*string{"name": &value}, Image: "foo:bar", }, }, } for _, test := range testCases { bc, err := initBuildConfig(serviceName, test.ServiceConfig, repo, branch) if err != nil { t.Error(errors.Wrap(err, "initBuildConfig failed")) } assertions := map[string]struct { field string value string }{ "Assert buildconfig source git URI": {bc.Spec.CommonSpec.Source.Git.URI, repo}, "Assert buildconfig source git Ref": {bc.Spec.CommonSpec.Source.Git.Ref, branch}, "Assert buildconfig source context dir": {bc.Spec.CommonSpec.Source.ContextDir, testDir + "/"}, // BuildConfig output image is named after service name. If image key is set than tag from that is used. "Assert buildconfig output name": {bc.Spec.CommonSpec.Output.To.Name, serviceName + ":" + GetImageTag(test.ServiceConfig.Image)}, "Assert buildconfig dockerfilepath": {bc.Spec.CommonSpec.Strategy.DockerStrategy.DockerfilePath, test.ServiceConfig.Dockerfile}, } for name, assertionTest := range assertions { if assertionTest.field != assertionTest.value { t.Errorf("%s Expected: %#v, got: %#v", name, assertionTest.value, assertionTest.field) } } if !reflect.DeepEqual(bc.Spec.CommonSpec.Strategy.DockerStrategy.Env, buildArgs) { t.Errorf("Expected: %#v, got: %#v", bc.Spec.CommonSpec.Strategy.DockerStrategy.Env, buildArgs) } } } // TestServiceWithoutPort this tests if Headless Service is created for services without Port (with label) func TestServiceWithoutPort(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", ServiceType: "Headless", } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } o := OpenShift{Kubernetes: kubernetes.Kubernetes{}} objects, err := o.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "o.Transform failed")) } if err := testutils.CheckForHeadless(objects); err != nil { t.Error(err) } } func TestRestartOnFailure(t *testing.T) { service := kobject.ServiceConfig{ Restart: "on-failure", } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } // define all test cases for RestartOnFailure function replicas := 2 testCase := map[string]struct { komposeObject kobject.KomposeObject opt kobject.ConvertOptions }{ // objects generated are deployment, service and replication controller "Do not Create DeploymentConfig (DC) with restart:'on-failure'": {komposeObject, kobject.ConvertOptions{IsDeploymentConfigFlag: true, Replicas: replicas}}, } for name, test := range testCase { t.Log("Test case:", name) o := OpenShift{} _, err := o.Transform(test.komposeObject, test.opt) if err == nil { t.Errorf("Expected an error, got %v instead", err) } } } // Tests if deployment strategy is being set to Recreate when volumes are // present func TestRecreateStrategyWithVolumesPresent(t *testing.T) { service := kobject.ServiceConfig{ ContainerName: "name", Image: "image", VolList: []string{"/tmp/volume"}, Volumes: []kobject.Volumes{{SvcName: "app", MountPath: "/tmp/volume", PVCName: "app-claim0"}}, } komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, } o := OpenShift{Kubernetes: kubernetes.Kubernetes{}} objects, err := o.Transform(komposeObject, kobject.ConvertOptions{CreateDeploymentConfig: true, Replicas: 1}) if err != nil { t.Error(errors.Wrap(err, "o.Transform failed")) } for _, obj := range objects { if deploymentConfig, ok := obj.(*deployapi.DeploymentConfig); ok { if deploymentConfig.Spec.Strategy.Type != deployapi.DeploymentStrategyTypeRecreate { t.Errorf("Expected %v as Strategy Type, got %v", deployapi.DeploymentStrategyTypeRecreate, deploymentConfig.Spec.Strategy.Type) } } } } func TestServiceExternalTrafficPolicy(t *testing.T) { groupName := "pod_group" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()}, } o := OpenShift{} objs, err := o.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if service, ok := obj.(*corev1.Service); ok { serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy) if serviceExternalTrafficPolicy != strings.ToLower(string(corev1.ServiceExternalTrafficPolicyTypeLocal)) { t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy) } serviceType := service.Spec.Type if serviceType != corev1.ServiceTypeLoadBalancer { t.Errorf("Expected LoadBalancer as service type, got %v", serviceType) } } } } func TestNamespaceGeneration(t *testing.T) { ns := "app" komposeObject := kobject.KomposeObject{ ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, Namespace: ns, } o := OpenShift{} objs, err := o.Transform(komposeObject, kobject.ConvertOptions{}) if err != nil { t.Error(errors.Wrap(err, "k.Transform failed")) } for _, obj := range objs { if namespace, ok := obj.(*api.Namespace); ok { if strings.ToLower(ns) != strings.ToLower(namespace.ObjectMeta.Name) { t.Errorf("Expected namespace name %v, got %v", ns, namespace.ObjectMeta.Name) } } if dep, ok := obj.(*deployapi.DeploymentConfig); ok { if dep.ObjectMeta.Namespace != ns { t.Errorf("Expected deployment namespace %v, got %v", ns, dep.ObjectMeta.Namespace) } } } } ================================================ FILE: pkg/transformer/openshift/utils.go ================================================ package openshift import ( "github.com/pkg/errors" "os/exec" "strings" ) // GetImageTag get tag name from image name // if no tag is specified return 'latest' func GetImageTag(image string) string { // format: registry_host:registry_port/repo_name/image_name:image_tag // example: // 1) myregistryhost:5000/fedora/httpd:version1.0 // 2) myregistryhost:5000/fedora/httpd // 3) myregistryhost/fedora/httpd:version1.0 // 4) myregistryhost/fedora/httpd // 5) fedora/httpd // 6) httpd imageAndTag := image i := strings.Split(image, "/") if len(i) >= 2 { imageAndTag = i[len(i)-1] } p := strings.Split(imageAndTag, ":") if len(p) == 2 { return p[1] } return "latest" } // GetAbsBuildContext returns build context relative to project root dir func GetAbsBuildContext(context string) (string, error) { cmd := exec.Command("git", "rev-parse", "--show-prefix") cmd.Dir = context var out strings.Builder var stderr strings.Builder cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { return "", errors.New(stderr.String()) } //convert output of command to string contextDir := strings.Trim(out.String(), "\n") return contextDir, nil } // HasGitBinary checks if the 'git' binary is available on the system func HasGitBinary() bool { _, err := exec.LookPath("git") return err == nil } // GetGitCurrentRemoteURL gets current git remote URI for the current git repo func GetGitCurrentRemoteURL(composeFileDir string) (string, error) { cmd := exec.Command("git", "ls-remote", "--get-url") cmd.Dir = composeFileDir var out strings.Builder var stderr strings.Builder cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { return "", errors.New(stderr.String()) } url := strings.TrimRight(out.String(), "\n") if !strings.HasSuffix(url, ".git") { url += ".git" } return url, nil } // GetGitCurrentBranch gets current git branch name for the current git repo func GetGitCurrentBranch(composeFileDir string) (string, error) { cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") cmd.Dir = composeFileDir var out strings.Builder var stderr strings.Builder cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { return "", errors.New(stderr.String()) } return strings.TrimRight(out.String(), "\n"), nil } ================================================ FILE: pkg/transformer/transformer.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package transformer import ( "github.com/kubernetes/kompose/pkg/kobject" "k8s.io/apimachinery/pkg/runtime" ) // Transformer interface defines transformer that is converting kobject to other resources type Transformer interface { // Transform converts KomposeObject to transformer specific objects. Transform(kobject.KomposeObject, kobject.ConvertOptions) ([]runtime.Object, error) } ================================================ FILE: pkg/transformer/utils.go ================================================ /* Copyright 2017 The Kubernetes Authors All rights reserved. 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. */ package transformer import ( "fmt" "os" "os/exec" "path" "path/filepath" "regexp" "strings" dockerlib "github.com/fsouza/go-dockerclient" "github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/utils/docker" "github.com/kubernetes/kompose/pkg/version" "github.com/pkg/errors" log "github.com/sirupsen/logrus" api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) // Selector used as labels and selector const Selector = "io.kompose.service" // Exists returns true if a file path exists. // Otherwise, returns false. func Exists(p string) bool { _, err := os.Stat(p) return err == nil } // CreateOutFile creates the file to write to if --out is specified func CreateOutFile(out string) (*os.File, error) { if len(out) == 0 { return nil, nil } // Creates directories if "out" contains nonexistent directories. if dir := filepath.Dir(out); !Exists(dir) { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return nil, errors.Wrap(err, "failed to create directories") } } f, err := os.Create(out) if err != nil { return nil, errors.Wrap(err, "failed to create file, os.Create failed") } return f, nil } // ParseVolume parses a given volume, which might be [name:][host:]container[:access_mode] func ParseVolume(volume string) (name, host, container, mode string, err error) { if containWindowsPath(volume) { return parseWindowsVolume(volume) } return parseVolume(volume) } func parseVolume(volume string) (name, host, container, mode string, err error) { separator := ":" // Parse based on ":" volumeStrings := strings.Split(volume, separator) if len(volumeStrings) == 0 { return } // Set name if existed if !isPath(volumeStrings[0]) { name = volumeStrings[0] volumeStrings = volumeStrings[1:] } // Check if *anything* has been passed if len(volumeStrings) == 0 { err = fmt.Errorf("invalid volume format: %s", volume) return } // Get the last ":" passed which is presumingly the "access mode" possibleAccessMode := volumeStrings[len(volumeStrings)-1] // Check to see if :Z or :z exists. We do not support SELinux relabeling at the moment. // See https://github.com/kubernetes/kompose/issues/176 // Otherwise, check to see if "rw" or "ro" has been passed if possibleAccessMode == "z" || possibleAccessMode == "Z" { log.Warnf("Volume mount \"%s\" will be mounted without labeling support. :z or :Z not supported", volume) mode = "" volumeStrings = volumeStrings[:len(volumeStrings)-1] } else if possibleAccessMode == "rw" || possibleAccessMode == "ro" { mode = possibleAccessMode volumeStrings = volumeStrings[:len(volumeStrings)-1] } // Check the volume format as well as host container = volumeStrings[len(volumeStrings)-1] volumeStrings = volumeStrings[:len(volumeStrings)-1] if len(volumeStrings) == 1 { host = volumeStrings[0] } if !isPath(container) || (len(host) > 0 && !isPath(host)) || len(volumeStrings) > 1 { err = fmt.Errorf("invalid volume format: %s", volume) return } return } // parseWindowsVolume parses window volume. // example: windows host mount to windows container // volume = dataVolumeName:C:\Users\Data:D:\config:rw // it can be parsed: // name=dataVolumeName, host=C:\Users\Data, container=D:\config, mode=rw // example: windows host mount to linux container // volume = dataVolumeName:C:\Users\Data:/etc/config:rw // it can be parsed: // name=dataVolumeName, host=C:\Users\Data, container=/etc/config, mode=rw func parseWindowsVolume(volume string) (name, host, container, mode string, err error) { var ( buffer, volumePaths []string volumeStrings = strings.Split(volume, ":") ) // extract path and leave order for _, fragment := range volumeStrings { switch { case containWindowsPath(fragment): if len(buffer) == 0 { err = fmt.Errorf("invalid windows volume %s", volume) return } driveLetter := buffer[len(buffer)-1] if len(driveLetter) != 1 { err = fmt.Errorf("invalid windows volume %s", volume) return } volumePaths = append(volumePaths, driveLetter+":"+fragment) buffer = buffer[:len(buffer)-1] case isPath(fragment): volumePaths = append(volumePaths, fragment) default: buffer = append(buffer, fragment) } } // set name and mode if exist if len(buffer) == 1 { if volumeStrings[0] == buffer[0] { name = buffer[0] } else if volumeStrings[len(volumeStrings)-1] == buffer[0] { mode = buffer[0] } } else if len(buffer) == 2 { name = buffer[0] mode = buffer[1] } else if len(buffer) > 2 { err = fmt.Errorf("invalid windows volume %s", volume) return } // Support in pass time // Check to see if :Z or :z exists. We do not support SELinux relabeling at the moment. // See https://github.com/kubernetes/kompose/issues/176 // Otherwise, check to see if "rw" or "ro" has been passed if mode == "z" || mode == "Z" { log.Warnf("Volume mount \"%s\" will be mounted without labeling support. :z or :Z not supported", volume) mode = "" } // Set host and container if exist if len(volumePaths) == 1 { container = volumePaths[0] } else if len(volumePaths) == 2 { host = volumePaths[0] container = volumePaths[1] } else { err = fmt.Errorf("invalid windows volume %s", volume) return } return } // containWindowsPath check whether it contains windows path. // windows path's separator is "\" func containWindowsPath(substring string) bool { return strings.Contains(substring, "\\") } // ParseIngressPath parse path for ingress. // eg. example.com/org -> example.com org func ParseIngressPath(url string) (string, string) { if strings.Contains(url, "/") { splits := strings.Split(url, "/") return splits[0], "/" + strings.Join(splits[1:], "/") } return url, "" } func isPath(substring string) bool { return strings.Contains(substring, "/") || substring == "." } // ConfigLabels configures label name alone func ConfigLabels(name string) map[string]string { return map[string]string{Selector: name} } // ConfigLabelsWithNetwork configures label and add Network Information in labels func ConfigLabelsWithNetwork(name string, net []string) map[string]string { labels := map[string]string{} labels[Selector] = name for _, n := range net { labels["io.kompose.network/"+n] = "true" } return labels //return map[string]string{Selector: name, "Network": net} } // ConfigAllLabels creates labels with service nam and deploy labels func ConfigAllLabels(name string, service *kobject.ServiceConfig) map[string]string { base := ConfigLabels(name) if service.DeployLabels != nil { for k, v := range service.DeployLabels { base[k] = v } } return base } // ConfigAnnotations configures annotations func ConfigAnnotations(service kobject.ServiceConfig) map[string]string { annotations := map[string]string{} for key, value := range service.Annotations { annotations[key] = value } annotations["kompose.cmd"] = strings.Join(os.Args, " ") versionCmd := exec.Command("kompose", "version") out, err := versionCmd.Output() if err != nil { errors.Wrap(err, "Failed to get kompose version") } annotations["kompose.version"] = strings.Trim(string(out), " \n") // If the version is blank (couldn't retrieve the kompose version for whatever reason) if annotations["kompose.version"] == "" { annotations["kompose.version"] = version.VERSION + " (" + version.GITCOMMIT + ")" } // if service.WithKomposeAnnotation = false, we remove **all** kompose annotations (io.kompose.*) if !service.WithKomposeAnnotation { for key := range annotations { if strings.HasPrefix(key, "kompose.") { delete(annotations, key) } } } return annotations } // Print either prints to stdout or to file/s func Print(name, path string, trailing string, data []byte, toStdout, generateJSON bool, f *os.File, provider string) (string, error) { file := "" // TODO: we should refactor / change this hack in the future once we have a better solution re := regexp.MustCompile(`(?s)status:\n.*`) data = re.ReplaceAll(data, nil) if generateJSON { file = fmt.Sprintf("%s-%s.json", name, trailing) } else { file = fmt.Sprintf("%s-%s.yaml", name, trailing) } if toStdout { fmt.Fprintf(os.Stdout, "%s\n", string(data)) return "", nil } else if f != nil { // Write all content to a single file f if _, err := f.WriteString(fmt.Sprintf("%s\n", string(data))); err != nil { return "", errors.Wrap(err, "f.WriteString failed, Failed to write %s to file: "+trailing) } f.Sync() } else { // Write content separately to each file file = filepath.Join(path, file) if err := os.WriteFile(file, data, 0644); err != nil { return "", errors.Wrap(err, "Failed to write %s: "+trailing) } log.Printf("%s file %q created", formatProviderName(provider), file) } return file, nil } // If Openshift, change to OpenShift! func formatProviderName(provider string) string { if strings.EqualFold(provider, "openshift") { return "OpenShift" } else if strings.EqualFold(provider, "kubernetes") { return "Kubernetes" } return provider } // EnvSort struct type EnvSort []api.EnvVar // Len returns the number of elements in the collection. func (env EnvSort) Len() int { return len(env) } // Less returns whether the element with index i should sort before // the element with index j. func (env EnvSort) Less(i, j int) bool { return env[i].Name < env[j].Name } // Swap swaps the elements with indexes i and j. func (env EnvSort) Swap(i, j int) { env[i], env[j] = env[j], env[i] } // GetComposeFileDir returns compose file directory func GetComposeFileDir(inputFiles []string) (string, error) { // Check if input files are specified if len(inputFiles) <= 0 { return "", errors.New("No input files specified") } // Lets assume all the docker-compose files are in the same directory inputFile, err := filepath.Abs(inputFiles[0]) if err != nil { return "", err } log.Debugf("Compose file dir: %s", filepath.Dir(inputFile)) return filepath.Dir(inputFile), nil } // BuildDockerImage builds docker image func BuildDockerImage(service kobject.ServiceConfig, name string) error { wd, err := os.Getwd() if err != nil { return err } log.Debug("Build image working dir is: ", wd) log.Debug("Build image service build is: ", service.Build) // Get the appropriate image source and name imagePath := service.Build if !path.IsAbs(service.Build) { imagePath = path.Join(wd, service.Build) } log.Debugf("Build image context is: %s", imagePath) if _, err := os.Stat(imagePath); err != nil { return errors.Wrapf(err, "%s is not a valid path for building image %s. Check if this dir exists.", service.Build, name) } imageName := name if service.Image != "" { imageName = service.Image } buildargs := []dockerlib.BuildArg{} for envName, envValue := range service.BuildArgs { var value string if envValue == nil { value = os.Getenv(envName) } else { value = *envValue } buildargs = append(buildargs, dockerlib.BuildArg{Name: envName, Value: value}) } // Connect to the Docker client client, err := docker.Client() if err != nil { return err } // Use the build struct function to build the image // Build the image! build := docker.Build{Client: *client} err = build.BuildImage(imagePath, imageName, service.Dockerfile, buildargs, service.BuildTarget) if err != nil { return err } return nil } // PushDockerImageWithOpt pushes docker image func PushDockerImageWithOpt(service kobject.ServiceConfig, serviceName string, opt kobject.ConvertOptions) error { if !opt.PushImage { // Don't do anything if registry is specified but push is disabled, just WARN about it if opt.PushImageRegistry != "" { log.Warnf("Push image registry '%s' is specified but push image is disabled, skipping pushing to repository", opt.PushImageRegistry) } return nil } log.Infof("Push image is enabled. Attempting to push image '%s'", service.Image) // Don't do anything if service.Image is blank, but at least WARN about it // else, let's push the image if service.Image == "" { log.Warnf("No image name has been passed for service %s, skipping pushing to repository", serviceName) return nil } image, err := docker.ParseImage(service.Image, opt.PushImageRegistry) if err != nil { return err } client, err := docker.Client() if err != nil { return err } if opt.PushImageRegistry != "" { log.Info("Push image registry is specified. Tag the image into registry firstly.") tag := docker.Tag{Client: *client} err = tag.TagImage(image) if err != nil { return err } } push := docker.Push{Client: *client} err = push.PushImage(image) if err != nil { return err } return nil } // CreateNamespace creates a Kubernetes namespace, which can be used in both: // Openshift and Kubernetes func CreateNamespace(namespace string) *api.Namespace { return &api.Namespace{ TypeMeta: metav1.TypeMeta{ Kind: "Namespace", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: namespace, }, } } // AssignNamespaceToObjects will add the namespace metadata to each object func AssignNamespaceToObjects(objs *[]runtime.Object, namespace string) { ns := "default" if namespace != "" { ns = namespace } var result []runtime.Object for _, obj := range *objs { if us, ok := obj.(metav1.Object); ok { us.SetNamespace(ns) } result = append(result, obj) } *objs = result } ================================================ FILE: pkg/transformer/utils_test.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package transformer import ( "fmt" "strings" "testing" ) func TestFormatProviderName(t *testing.T) { if formatProviderName("openshift") != "OpenShift" { t.Errorf("Got %s, expected OpenShift", formatProviderName("openshift")) } if formatProviderName("kubernetes") != "Kubernetes" { t.Errorf("Got %s, expected Kubernetes", formatProviderName("kubernetes")) } } // When passing "z" or "Z" we expect "" back. func TestZParseVolumeLabeling(t *testing.T) { testCase := "/foobar:/foobar:Z" windowVolumeTestCase := "C:\\foobar:/foobar:Z" _, _, _, mode, err := ParseVolume(testCase) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", testCase, err) } if mode != "" { t.Errorf("In test case %q, returned mode %s, expected \"\"", testCase, mode) } _, _, _, mode, err = ParseVolume(windowVolumeTestCase) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", windowVolumeTestCase, err) } if mode != "" { t.Errorf("In test case %q, returned mode %s, expected \"\"", windowVolumeTestCase, mode) } } func TestParseWindowsVolumeMountLinuxContainer(t *testing.T) { name := "datavolume" windowsHosts := "C:\\Users" linuxContainer := "/etc/configs/" mode := "rw" tests := []struct { test, volume, name, host, container, mode string }{ { "name:host:container:mode", fmt.Sprintf("%s:%s:%s:%s", name, windowsHosts, linuxContainer, mode), name, windowsHosts, linuxContainer, mode, }, { "host:container:mode", fmt.Sprintf("%s:%s:%s", windowsHosts, linuxContainer, mode), "", windowsHosts, linuxContainer, mode, }, { "name:container:mode", fmt.Sprintf("%s:%s:%s", name, linuxContainer, mode), name, "", linuxContainer, mode, }, { "name:host:container", fmt.Sprintf("%s:%s:%s", name, windowsHosts, linuxContainer), name, windowsHosts, linuxContainer, "", }, { "host:container", fmt.Sprintf("%s:%s", windowsHosts, linuxContainer), "", windowsHosts, linuxContainer, "", }, { "container:mode", fmt.Sprintf("%s:%s", linuxContainer, mode), "", "", linuxContainer, mode, }, { "name:container", fmt.Sprintf("%s:%s", name, linuxContainer), name, "", linuxContainer, "", }, { "container", linuxContainer, "", "", linuxContainer, "", }, } for _, test := range tests { name, host, container, mode, err := ParseVolume(test.volume) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", test.test, err) } if name != test.name { t.Errorf("In test case %q, returned volume name %s, expected %s", test.test, name, test.name) } if host != test.host { t.Errorf("In test case %q, returned host path %s, expected %s", test.test, host, test.host) } if container != test.container { t.Errorf("In test case %q, returned container path %s, expected %s", test.test, container, test.container) } if mode != test.mode { t.Errorf("In test case %q, returned access mode %s, expected %s", test.test, mode, test.mode) } } } func TestParseWindowsVolumeMountWindowsContainer(t *testing.T) { name := "datavolume" windowsHosts := "C:\\Users" windowsContainer := "D:\\Users" mode := "rw" tests := []struct { test, volume, name, host, container, mode string }{ { "name:host:container:mode", fmt.Sprintf("%s:%s:%s:%s", name, windowsHosts, windowsContainer, mode), name, windowsHosts, windowsContainer, mode, }, { "host:container:mode", fmt.Sprintf("%s:%s:%s", windowsHosts, windowsContainer, mode), "", windowsHosts, windowsContainer, mode, }, { "name:container:mode", fmt.Sprintf("%s:%s:%s", name, windowsContainer, mode), name, "", windowsContainer, mode, }, { "name:host:container", fmt.Sprintf("%s:%s:%s", name, windowsHosts, windowsContainer), name, windowsHosts, windowsContainer, "", }, { "host:container", fmt.Sprintf("%s:%s", windowsHosts, windowsContainer), "", windowsHosts, windowsContainer, "", }, { "container:mode", fmt.Sprintf("%s:%s", windowsContainer, mode), "", "", windowsContainer, mode, }, { "name:container", fmt.Sprintf("%s:%s", name, windowsContainer), name, "", windowsContainer, "", }, { "container", windowsContainer, "", "", windowsContainer, "", }, } for _, test := range tests { name, host, container, mode, err := ParseVolume(test.volume) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", test.test, err) } if name != test.name { t.Errorf("In test case %q, returned volume name %s, expected %s", test.test, name, test.name) } if host != test.host { t.Errorf("In test case %q, returned host path %s, expected %s", test.test, host, test.host) } if container != test.container { t.Errorf("In test case %q, returned container path %s, expected %s", test.test, container, test.container) } if mode != test.mode { t.Errorf("In test case %q, returned access mode %s, expected %s", test.test, mode, test.mode) } } } func TestParseVolume(t *testing.T) { name1 := "datavolume" host1 := "./cache" host2 := "~/configs" container1 := "/tmp/cache" container2 := "/etc/configs/" mode := "rw" tests := []struct { test, volume, name, host, container, mode string }{ { "name:host:container:mode", fmt.Sprintf("%s:%s:%s:%s", name1, host1, container1, mode), name1, host1, container1, mode, }, { "host:container:mode", fmt.Sprintf("%s:%s:%s", host2, container2, mode), "", host2, container2, mode, }, { "name:container:mode", fmt.Sprintf("%s:%s:%s", name1, container1, mode), name1, "", container1, mode, }, { "name:host:container", fmt.Sprintf("%s:%s:%s", name1, host1, container1), name1, host1, container1, "", }, { "host:container", fmt.Sprintf("%s:%s", host1, container1), "", host1, container1, "", }, { "container:mode", fmt.Sprintf("%s:%s", container2, mode), "", "", container2, mode, }, { "name:container", fmt.Sprintf("%s:%s", name1, container1), name1, "", container1, "", }, { "container", container2, "", "", container2, "", }, } for _, test := range tests { name, host, container, mode, err := ParseVolume(test.volume) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", test.test, err) } if name != test.name { t.Errorf("In test case %q, returned volume name %s, expected %s", test.test, name, test.name) } if host != test.host { t.Errorf("In test case %q, returned host path %s, expected %s", test.test, host, test.host) } if container != test.container { t.Errorf("In test case %q, returned container path %s, expected %s", test.test, container, test.container) } if mode != test.mode { t.Errorf("In test case %q, returned access mode %s, expected %s", test.test, mode, test.mode) } } } func TestGetComposeFileDir(t *testing.T) { output, err := GetComposeFileDir([]string{"foobar/docker-compose.yaml"}) if err != nil { t.Errorf("Error with GetComposeFileDir %v", err) } if !strings.Contains(output, "foobar") { t.Errorf("Expected $PWD/foobar, got %v", output) } } ================================================ FILE: pkg/utils/archive/tar.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package archive import ( "archive/tar" "io" "os" "path/filepath" "strings" ) /* CreateTarball creates a tarball for source and dumps it to target path Function modified and added from https://github.com/mholt/archiver/blob/master/tar.go */ func CreateTarball(source, target string) error { tarfile, err := os.Create(target) if err != nil { return err } defer tarfile.Close() tarball := tar.NewWriter(tarfile) defer tarball.Close() info, err := os.Stat(source) if err != nil { return nil } var baseDir string if info.IsDir() { baseDir = filepath.Base(source) } return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { if baseDir == path { return nil } if err != nil { return err } header, err := tar.FileInfoHeader(info, info.Name()) if err != nil { return err } if baseDir != "" { if strings.HasSuffix(source, "/") { header.Name = strings.TrimPrefix(path, source) } else { header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) } //println("Header name", header.Name) } if err := tarball.WriteHeader(header); err != nil { return err } if info.IsDir() { return nil } file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(tarball, file) return err }) } ================================================ FILE: pkg/utils/docker/build.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( "bytes" "fmt" "os" "os/exec" "path" "strconv" "strings" dockerlib "github.com/fsouza/go-dockerclient" "github.com/kubernetes/kompose/pkg/utils/archive" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // Build will provide methods for interaction with API regarding building images type Build struct { Client dockerlib.Client } /* BuildImage builds a Docker image via the Docker API or Docker CLI. Takes the source directory and image name and then builds the appropriate image. Tarball is utilized in order to make building easier. if the DOCKER_BUILDKIT is '1', then we will use the docker CLI to build the image */ func (c *Build) BuildImage(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, buildTarget string) error { log.Infof("Building image '%s' from directory '%s'", image, path.Base(source)) outputBuffer := bytes.NewBuffer(nil) var err error if usecli, _ := strconv.ParseBool(os.Getenv("DOCKER_BUILDKIT")); usecli { err = buildDockerCli(source, image, dockerfile, buildargs, outputBuffer, buildTarget) } else { err = c.buildDockerClient(source, image, dockerfile, buildargs, outputBuffer, buildTarget) } log.Debugf("Image %s build output:\n%s", image, outputBuffer) if err != nil { return errors.Wrap(err, "Unable to build image. For more output, use -v or --verbose when converting.") } log.Infof("Image '%s' from directory '%s' built successfully", image, path.Base(source)) return nil } func (c *Build) buildDockerClient(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer, buildTarget string) error { // Create a temporary file for tarball image packaging tmpFile, err := os.CreateTemp(os.TempDir(), "kompose-image-build-") if err != nil { return err } log.Debugf("Created temporary file %v for Docker image tarballing", tmpFile.Name()) // Create a tarball of the source directory in order to build the resulting image err = archive.CreateTarball(strings.Join([]string{source, ""}, "/"), tmpFile.Name()) if err != nil { return errors.Wrap(err, "Unable to create a tarball") } // Load the file into memory tarballSource, err := os.Open(tmpFile.Name()) if err != nil { return errors.Wrap(err, "Unable to load tarball into memory") } // Let's create all the options for the image building. opts := dockerlib.BuildImageOptions{ Name: image, InputStream: tarballSource, OutputStream: outputBuffer, Dockerfile: dockerfile, BuildArgs: buildargs, Target: buildTarget, } // Build it! return c.Client.BuildImage(opts) } func buildDockerCli(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer, buildTarget string) error { args := []string{"build", "-t", image} if dockerfile != "" { args = append(args, "-f", dockerfile) } for _, buildarg := range buildargs { args = append(args, "--build-arg", fmt.Sprintf("%s=%s", buildarg.Name, buildarg.Value)) } args = append(args, source) if buildTarget != "" { args = append(args, fmt.Sprintf("--target=%s", buildTarget)) } cmd := exec.Command("docker", args...) cmd.Stdout = outputBuffer cmd.Stderr = outputBuffer log.Debugf("Image %s build calling command %v", image, cmd) return cmd.Run() } ================================================ FILE: pkg/utils/docker/client.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( "os" docker "github.com/fsouza/go-dockerclient" ) // Client connects to Docker client on host func Client() (*docker.Client, error) { var ( err error client *docker.Client ) dockerHost := os.Getenv("DOCKER_HOST") if len(dockerHost) > 0 { // Create client instance from Docker's environment variables: // DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH client, err = docker.NewClientFromEnv() } else { // Default unix socket end-point endpoint := "unix:///var/run/docker.sock" client, err = docker.NewClient(endpoint) } if err != nil { return client, err } return client, nil } ================================================ FILE: pkg/utils/docker/image.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( "path" dockerparser "github.com/novln/docker-parser" ) // Image contains the basic information parsed from full image name // see github.com/novln/docker-parser Reference type Image struct { Name string // the image's name (ie: debian[:8.2]) ShortName string // the image's name (ie: debian) Tag string // the image's tag (or digest) Registry string // the image's registry. (ie: host[:port]) Repository string // the image's repository. (ie: registry/name) Remote string // the image's remote identifier. (ie: registry/name[:tag]) } // NewImageFromParsed method returns the docker image from the docker parser reference func NewImageFromParsed(parsed *dockerparser.Reference) Image { return Image{ Name: parsed.Name(), ShortName: parsed.ShortName(), Tag: parsed.Tag(), Registry: parsed.Registry(), Repository: parsed.Repository(), Remote: parsed.Remote(), } } // ParseImage Using https://github.com/novln/docker-parser in order to parse the appropriate name and registry. // 1. Return default registry when the registry is not specified from image // 2. Return target registry when the registry is specified from command line func ParseImage(fullImageName string, targetRegistry string) (Image, error) { var image Image // First parse to fill default fields for image // See github.com/novln/docker-parser/docker/reference.go parsedImage, err := dockerparser.Parse(fullImageName) if err != nil { return image, err } // Registry from command argument is high priority than parsed from name of image. if targetRegistry != "" { // Parse again for validating registry fullImageName = path.Join(targetRegistry, parsedImage.Name()) parsedImage, err = dockerparser.Parse(fullImageName) if err != nil { return image, err } } image = NewImageFromParsed(parsedImage) return image, nil } ================================================ FILE: pkg/utils/docker/image_test.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( "reflect" "testing" ) func TestParseImage(t *testing.T) { type args struct { fullImageName string targetRegistry string } tests := []struct { name string args args want Image wantErr bool }{ { "Given empty registry Then default registry expected", args{ "foo/bar", "", }, Image{ "foo/bar:latest", "foo/bar", "latest", "docker.io", "docker.io/foo/bar", "docker.io/foo/bar:latest", }, false, }, { "Given registry from image Then parsed registry expected", args{ "docker.io/foo/bar", "", }, Image{ "foo/bar:latest", "foo/bar", "latest", "docker.io", "docker.io/foo/bar", "docker.io/foo/bar:latest", }, false, }, { "Given target registry Then target registry expected", args{ "foo/bar", "localhost:5000", }, Image{ "foo/bar:latest", "foo/bar", "latest", "localhost:5000", "localhost:5000/foo/bar", "localhost:5000/foo/bar:latest", }, false, }, { "Given registry from image and target registry Then target registry expected", args{ "docker.io/foo/bar", "localhost:5000", }, Image{ "foo/bar:latest", "foo/bar", "latest", "localhost:5000", "localhost:5000/foo/bar", "localhost:5000/foo/bar:latest", }, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseImage(tt.args.fullImageName, tt.args.targetRegistry) if (err != nil) != tt.wantErr { t.Errorf("ParseImage() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseImage() got = %+v, want %+v", got, tt.want) } }) } } ================================================ FILE: pkg/utils/docker/push.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( "bytes" dockerlib "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // Push will provide methods for interaction with API regarding pushing images type Push struct { Client dockerlib.Client } /* PushImage pushes a Docker image via the Docker API. Takes the image name, parses the URL details and then push based on environment authentication credentials. */ func (c *Push) PushImage(image Image) error { log.Infof("Pushing image '%s' to registry '%s'", image.Name, image.Registry) // Let's setup the push and authentication options outputBuffer := bytes.NewBuffer(nil) options := dockerlib.PushImageOptions{ Tag: image.Tag, Name: image.Repository, Registry: image.Registry, OutputStream: outputBuffer, } // Retrieve the authentication configuration file // Files checked as per https://godoc.org/github.com/fsouza/go-dockerclient#NewAuthConfigurationsFromFile // $DOCKER_CONFIG/config.json, $HOME/.docker/config.json , $HOME/.dockercfg credentials, err := dockerlib.NewAuthConfigurationsFromDockerCfg() if err != nil { log.Warn(errors.Wrap(err, "Unable to retrieve .docker/config.json authentication details. Check that 'docker login' works successfully on the command line.")) } else { handleDockerRegistry(credentials) } // Find the authentication matched to registry auth, ok := credentials.Configs[image.Registry] if !ok { // Fallback to unauthenticated access in case if no auth credentials are retrieved log.Infof("Authentication credential of registry '%s' is not found. Will try push without authentication.", image.Registry) // Header X-Registry-Auth is required // Or API error (400): Bad parameters and missing X-Registry-Auth: EOF will throw // Just to make not empty struct auth = dockerlib.AuthConfiguration{Username: "docker"} } log.Debugf("Pushing image with options %+v", options) err = c.Client.PushImage(options, auth) if err != nil { log.Errorf("Unable to push image '%s' to registry '%s'. Error: %s", image.Name, image.Registry, err) return errors.New("unable to push docker image(s). Check that `docker login` works successfully on the command line") } log.Debugf("Image '%+v' push output:\n%s", image, outputBuffer) log.Infof("Successfully pushed image '%s' to registry '%s'", image.Name, image.Registry) return nil } // handleDockerRegistry adapt legacy docker registry address // After docker login to docker.io, there must be https://index.docker.io/v1/ in config.json of authentication // Reference: https://docs.docker.com/engine/api/v1.23/ // // > However (for legacy reasons) the “official” Docker, Inc. hosted registry // > must be specified with both a “https://” prefix and a “/v1/” suffix // > even though Docker will prefer to use the v2 registry API. func handleDockerRegistry(auth *dockerlib.AuthConfigurations) { const address = "docker.io" const legacyAddress = "https://index.docker.io/v1/" if legacyAddressConfig, ok := auth.Configs[legacyAddress]; ok { auth.Configs[address] = legacyAddressConfig } } ================================================ FILE: pkg/utils/docker/tag.go ================================================ /* Copyright 2016 The Kubernetes Authors All rights reserved 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. */ package docker import ( dockerlib "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) // Tag will provide methods for interaction with API regarding tagging images type Tag struct { Client dockerlib.Client } // TagImage function is responsible for tagging the docker image func (c *Tag) TagImage(image Image) error { options := dockerlib.TagImageOptions{ Tag: image.Tag, Repo: image.Repository, } log.Infof("Tagging image '%s' into repository '%s'", image.Name, image.Repository) err := c.Client.TagImage(image.ShortName, options) if err != nil { log.Errorf("Unable to tag image '%s' into repository '%s'. Error: %s", image.Name, image.Registry, err) return errors.New("unable to tag docker image(s)") } log.Infof("Successfully tagged image '%s'", image.Remote) return nil } ================================================ FILE: pkg/version/version.go ================================================ package version var ( // VERSION is version number that will be displayed when running ./kompose version VERSION = "1.38.0" // GITCOMMIT is hash of the commit that will be displayed when running ./kompose version // this will be overwritten when running build like this: go build -ldflags="-X github.com/kubernetes/kompose/pkg/version.GITCOMMIT=$(GITCOMMIT)" // HEAD is default indicating that this was not set during build GITCOMMIT = "HEAD" ) ================================================ FILE: script/check-gofmt.sh ================================================ #!/bin/bash # Copyright 2017 The Kubernetes Authors All rights reserved. # # 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. # This checks if all go source files in current directory are format using gofmt # Ignore vendor directory that's NOT in the main path GO_FILES=$(find . -path ./vendor -prune -o -name '*.go' -print ) for file in $GO_FILES; do gofmtOutput=$(gofmt -l "$file") if [ "$gofmtOutput" ]; then errors+=("$gofmtOutput") fi done if [ ${#errors[@]} -eq 0 ]; then echo "gofmt OK" else echo "gofmt ERROR - These files are not formatted by gofmt:" for err in "${errors[@]}"; do echo "$err" done exit 1 fi ================================================ FILE: script/manual-docs-sync.sh ================================================ #!/usr/bin/env bash ## README: ## This script is ran by running: ## cd script ## ./manual-docs-sync.sh ## ## This will take all documentation from the /docs folder of the main dir and push to the gh-pages branch (granted you have access) DOCS_REPO_NAME="kompose" DOCS_REPO_URL="git@github.com:kubernetes/kompose.git" DOCS_BRANCH="gh-pages" DOCS_FOLDER="docs" # clone the repo git clone "$DOCS_REPO_URL" "$DOCS_REPO_NAME" # change to that directory (to prevent accidental pushing to main, etc.) cd "$DOCS_REPO_NAME" # switch to gh-pages and grab the docs folder from main git checkout gh-pages git checkout main -- docs # Copy it all over to the current directory cp -r docs/* . rm -r docs git add --all # Check if anything changed, and if it's the case, push to origin/main. if git commit -m 'Update docs' -m "Synchronize documentation against website" ; then git push fi # cd back to the original root folder cd .. ================================================ FILE: script/release.sh ================================================ #!/usr/bin/env bash # Copyright 2017 The Kubernetes Authors All rights reserved. # # 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. # Constants. Enter relevant repo information here. UPSTREAM_REPO="kubernetes" CLI="kompose" usage() { echo "This will prepare $CLI for release!" echo "" echo "Requirements:" echo " git" echo " gh" echo " " echo "Not only that, but you must have permission for:" echo " Tagging releases within Github" echo "" } requirements() { if ! hash git 2>/dev/null; then echo "ERROR: No git." exit 0 fi } # Make sure that upstream had been added to the repo init_sync() { CURRENT_ORIGIN=`git config --get remote.origin.url` CURRENT_UPSTREAM=`git config --get remote.upstream.url` ORIGIN="git@github.com:$ORIGIN_REPO/$CLI.git" UPSTREAM="git@github.com:$UPSTREAM_REPO/$CLI.git" if [ $CURRENT_ORIGIN != $ORIGIN ]; then echo "Origin repo must be set to $ORIGIN" exit 0 fi if [ $CURRENT_UPSTREAM != $UPSTREAM ]; then echo "Upstream repo must be set to $UPSTREAM" exit 0 fi git checkout main git fetch upstream git merge upstream/main git checkout -b release-$1 } replaceversion() { echo "Replaced version in pkg/version/version.go" sed -i "s/$1/$2/g" pkg/version/version.go || gsed -i "s/$1/$2/g" pkg/version/version.go echo "Replaced version in README.md" sed -i "s/$1/$2/g" README.md || gsed -i "s/$1/$2/g" README.md echo "Replaced version in docs/installation.md" sed -i "s/$1/$2/g" docs/installation.md || gsed -i "s/$1/$2/g" docs/installation.md sed -i "s/$1/$2/g" site/docs/installation.md || gsed -i "s/$1/$2/g" site/docs/installation.md echo "Replaced version in build/VERSION" sed -i "s/$1/$2/g" build/VERSION || gsed -i "s/$1/$2/g" build/VERSION echo "Ignore above errors if you're using macOS" } build_binaries() { make cross } create_tarballs() { # cd into the bin directory so we don't have '/bin' inside the tarball cd bin for f in * do tar cvzf $f.tar.gz $f done cd .. } git_commit() { BRANCH=`git symbolic-ref --short HEAD` if [ -z "$BRANCH" ]; then echo "Unable to get branch name, is this even a git repo?" return 1 fi echo "Branch: " $BRANCH git add . git commit -m "$1 Release" git push origin $BRANCH gh pr create echo "" echo "PR opened against main to update version" echo "MERGE THIS BEFORE CONTINUING" echo "" } git_pull() { git pull } git_sync() { git fetch upstream main git rebase upstream/main } generate_install_guide() { echo " # Installation __Linux and macOS:__ \`\`\`sh # Linux curl -L https://github.com/kubernetes/kompose/releases/download/v$1/kompose-linux-amd64 -o kompose # Linux ARM64 curl -L https://github.com/kubernetes/kompose/releases/download/v$1/kompose-linux-arm64 -o kompose # macOS curl -L https://github.com/kubernetes/kompose/releases/download/v$1/kompose-darwin-amd64 -o kompose # macOS ARM64 curl -L https://github.com/kubernetes/kompose/releases/download/v$1/kompose-darwin-arm64 -o kompose chmod +x kompose sudo mv ./kompose /usr/local/bin/kompose \`\`\` __Windows:__ Download from [GitHub](https://github.com/kubernetes/kompose/releases/download/v$1/kompose-windows-amd64.exe) and add the binary to your PATH. __Checksums:__ | Filename | SHA256 Hash | | ------------- |:-------------:|" > install_guide.txt touch bin/SHA256_SUM for f in bin/* do HASH=`sha256sum $f | head -n1 | awk '{print $1;}'` NAME=`echo $f | sed "s,bin/,,g"` echo "[$NAME](https://github.com/kubernetes/kompose/releases/download/v$1/$NAME) | $HASH" >> install_guide.txt echo "$HASH $NAME" >> bin/SHA256_SUM done } push() { echo "!!PLEASE READ!! 1. Say YES to GitHub generating the release notes 2. Append install_guide.txt to the TOP of the release notes when prompted 3. Double check that the binaries have been UPLOADED! " gh release create v$1 bin/* echo "DONE" echo "DOUBLE CHECK IT:" echo "!!!" echo "https://github.com/$UPSTREAM_REPO/$CLI/releases/edit/$1" echo "!!!" } clean() { rm install_guide.txt rm -r bin/* } main() { local cmd=$1 usage requirements echo "What is your Github username? (location of your $CLI fork)" read ORIGIN_REPO echo "You entered: $ORIGIN_REPO" echo "" echo "" echo "First, please enter the version of the NEW release: " read VERSION echo "You entered: $VERSION" echo "" echo "" echo "Second, please enter the version of the LAST release: " read PREV_VERSION echo "You entered: $PREV_VERSION" echo "" clear echo "Now! It's time to go through each step of releasing $CLI!" echo "If one of these steps fails / does not work, simply re-run ./release.sh" echo "Re-enter the information at the beginning and continue on the failed step" echo "" PS3='Please enter your choice: ' options=( "Initial sync with upstream" "Replace version number" "Create PR" "Sync with upstream" "Build binaries" "Create tarballs" "Generate install guide" "Upload the binaries and push to GitHub release page" "Update the website" "Clean" "Quit") select opt in "${options[@]}" do echo "" case $opt in "Initial sync with upstream") init_sync $VERSION ;; "Replace version number") replaceversion $PREV_VERSION $VERSION ;; "Create PR") git_commit $VERSION ;; "Sync with upstream") git_sync ;; "Build binaries") build_binaries ;; "Create tarballs") create_tarballs ;; "Generate install guide") generate_install_guide $VERSION ;; "Upload the binaries and push to GitHub release page") push $VERSION ;; "Update the website") echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "Run ./script/manual-docs-sync.sh on the main branch" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" ;; "Clean") clean $VERSION ;; "Quit") clear break ;; *) echo invalid option;; esac echo "" done } main "$@" ================================================ FILE: script/test/README.md ================================================ # Functional Test ### Requirements Install `jq - commandline JSON processor` with minimum version of 1.5 ### Running tests Test running ./cmd/tests.sh ================================================ FILE: script/test/cmd/cmd_test.go ================================================ package cmd import ( "bytes" "fmt" "os" "os/exec" "testing" ) var ProjectPath = "$GOPATH/src/github.com/kubernetes/kompose/" var BinaryLocation = os.ExpandEnv(ProjectPath + "kompose") func Test_stdin(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } kjson := `{"version": "2","services": {"redis": {"image": "redis:3.0","ports": ["6379"]}}}` cmdStr := fmt.Sprintf("%s convert --stdout -j -f - < /dev/null; then KOMPOSE_ROOT=$(greadlink -f $(dirname "${BASH_SOURCE}")/../../..) else KOMPOSE_ROOT=$(readlink -f $(dirname "${BASH_SOURCE}")/../../..) fi stat="stat" if type "gstat" > /dev/null; then stat="gstat" fi source $KOMPOSE_ROOT/script/test/cmd/globals.sh # setup all the things needed to run tests function convert::init() { mkdir -p $TEMP_DIR SUCCESS_MSGS="" FAIL_MSGS="" } readonly -f convert::init # remove all the temporary files created for test function convert::teardown() { rm -rf $TEMP_DIR SUCCESS_MSGS="" FAIL_MSGS="" } readonly -f convert::teardown # print about test start information function convert::start_test() { convert::init echo -e "\n\n===> Starting test <===" echo $@ } readonly -f convert::start_test # print in green about the test being passed function convert::print_pass() { tput setaf 2 tput bold echo -en "PASS: $@" tput sgr0 } readonly -f convert::print_pass # print in red about the test failed function convert::print_fail() { tput setaf 1 tput bold echo -en "FAIL: $@" tput sgr0 } readonly -f convert::print_fail # run a cmd, which saves stdout output to TEMP_STDOUT # and saves errors to TEMP_STDERR files and returns exit status function convert::run_cmd() { cmd=$@ $cmd 2>$TEMP_STDERR >$TEMP_STDOUT return $? } readonly -f convert::run_cmd function convert::expect_cmd_success() { local cmd=$1 convert::start_test "convert::expect_cmd_success: Running: '${cmd}'" convert::run_cmd $cmd exit_status=$? if [ $exit_status -ne 0 ]; then FAIL_MSGS=$FAIL_MSGS"exit status: $exit_status\n"; return $exit_status; fi } # run the command and match the output to the existing file # if error then save error string in FAIL_MSGS # if success save pass string in SUCCESS_MSGS function convert::match_output() { local cmd=$1 local expected_output=$2 convert::run_cmd $cmd exit_status=$? if [ $exit_status -ne 0 ]; then FAIL_MSGS=$FAIL_MSGS"exit status: $exit_status\n"; return $exit_status; fi match=$(dyff between --ignore-order-changes --set-exit-code $expected_output $TEMP_STDOUT) if [ $? -eq 0 ]; then SUCCESS_MSGS=$SUCCESS_MSGS"converted output matches\n"; return 0; else FAIL_MSGS=$FAIL_MSGS"converted output does not match\n"; echo $match; return 1; fi } readonly -f convert::match_output # function called from outside which accepts cmd to run and # file to compare output with function convert::expect_success() { local cmd=$1 local expected_output=$2 convert::start_test "convert::expect_success: Running: '${cmd}' expected_output: '${expected_output}'" convert::match_output "$cmd" "$expected_output" if [ $? -ne 0 ]; then convert::print_fail $FAIL_MSGS; convert::teardown; EXIT_STATUS=1; return 1; else convert::print_pass $SUCCESS_MSGS; fi # check if no warnings are generated? If yes then fail warnings=$($stat -c%s $TEMP_STDERR) if [ $warnings -ne 0 ]; then convert::print_fail "warnings given: $(cat $TEMP_STDERR)"; EXIT_STATUS=1; fi convert::teardown } readonly -f convert::expect_success # function called from outside, which accepts cmd to run, # expected output file and warnings if any function convert::expect_success_and_warning() { local cmd=$1 local expected_output=$2 local expected_warning=$3 convert::start_test "convert::expect_success_and_warning: Running: '${cmd}' expected_output: '${expected_output}' expected_warning: '${expected_warning}'" convert::match_output "$cmd" "$expected_output" if [ $? -ne 0 ]; then convert::print_fail $FAIL_MSGS; convert::teardown; EXIT_STATUS=1; return 1; else convert::print_pass $SUCCESS_MSGS; fi grep -i "$expected_warning" $TEMP_STDERR > /dev/null local exit_status=$? if [ $exit_status -ne 0 ]; then convert::print_fail "no warning found: '$expected_warning'"; EXIT_STATUS=1; else convert::print_pass "warning found: '$expected_warning'"; fi convert::teardown return $exit_status } readonly -f convert::expect_success_and_warning # function called from outside, which accepts cmd to run, # expects warning, without caring if the cmd ran passed or failed function convert::expect_warning() { local cmd=$1 local expected_warning=$2 convert::start_test "convert::expect_warning: Running: '${cmd}' expected_warning: '${expected_warning}'" $cmd 2>$TEMP_STDERR >$TEMP_STDOUT grep -i "$expected_warning" $TEMP_STDERR > /dev/null local exit_status=$? if [ $exit_status -ne 0 ]; then convert::print_fail "no warning found: '$expected_warning'"; EXIT_STATUS=1; else convert::print_pass "warning found: '$expected_warning'"; fi convert::teardown return $exit_status } readonly -f convert::expect_warning # function called from outside, which accepts cmd to run, # expects failure, if the command passes then errors out. function convert::expect_failure() { local cmd=$1 convert::start_test "convert::expect_failure: Running: '${cmd}'" convert::run_cmd $cmd exit_status=$? if [ $exit_status -eq 0 ]; then convert::print_fail "no error output, returned exit status 0"; EXIT_STATUS=1; else convert::print_pass "errored out with exit status: $exit_status"; fi convert::teardown return $exit_status } readonly -f convert::expect_failure # see if the given files exists function utils::file_exists() { for file in "$@" do exit_status=$([ -f $file ]; echo $?) if [ $exit_status -ne 0 ]; then convert::print_fail "$file does not exist\n"; EXIT_STATUS=1; else convert::print_pass "$file exists\n"; fi done } readonly -f utils::file_exists # delete given files one by one function utils::remove_files() { for file in "$@" do rm $file done } readonly -f utils::remove_files function convert::check_artifacts_generated() { local cmd=$1 convert::start_test "convert::check_artifacts_generated: Running: '${cmd}'" convert::run_cmd $cmd # passing all args except the first one utils::file_exists "${@:2}" utils::remove_files "${@:2}" convert::teardown return $exit_status } readonly -f convert::check_artifacts_generated ================================================ FILE: script/test/cmd/tests.sh ================================================ #!/bin/bash # Copyright 2017 The Kubernetes Authors All rights reserved. # # 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 pe#rmissions and # limitations under the License. # for mac if type "greadlink" > /dev/null; then KOMPOSE_ROOT=$(greadlink -f $(dirname "${BASH_SOURCE}")/../../..) else KOMPOSE_ROOT=$(readlink -f $(dirname "${BASH_SOURCE}")/../../..) fi # for mac (GNU realpath) realpath_cmd="realpath" if type "grealpath" > /dev/null 2>&1; then realpath_cmd="grealpath" fi # Check for container runtime (docker or podman) # These are needed for --build local tests CONTAINER_RUNTIME="" BUILD_CMD_FLAGS="" if docker info > /dev/null 2>&1; then CONTAINER_RUNTIME="docker" elif podman info > /dev/null 2>&1; then CONTAINER_RUNTIME="podman" BUILD_CMD_FLAGS="--build-command 'podman build' --push-command 'podman push'" fi source $KOMPOSE_ROOT/script/test/cmd/lib.sh # Get current branch and remote url of git repository branch=$(git branch | grep \* | cut -d ' ' -f2-) uri=$(git config --get remote.origin.url) if [[ $uri != *".git"* ]]; then uri="${uri}.git" fi # Get version version=`kompose version` # Warning Template warning="Buildconfig using $uri::$branch as source." # Replacing variables with current branch and uri sed -e "s;%VERSION%;$version;g" -e "s;%URI%;$uri;g" -e "s;%REF%;$branch;g" $KOMPOSE_ROOT/script/test/fixtures/nginx-node-redis/output-os-template.json > /tmp/output-os.json # ## TEST V2 DIR="v2" k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/$DIR/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/$DIR/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/$DIR/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/$DIR/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" convert::expect_success_and_warning "$os_cmd" "$os_output" ## TEST V3 DIR="v3.0" k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/$DIR/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/$DIR/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/$DIR/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/$DIR/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" convert::expect_success_and_warning "$os_cmd" "$os_output" ###### # Test the output file behavior of kompose convert # Default behavior without -o convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -j" "redis-deployment.json" "redis-service.json" "web-deployment.json" "web-service.json" # Behavior with -o convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -o output_file -j" "output_file" # Behavior with -o convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -o $TEMP_DIR -j" "$TEMP_DIR/redis-deployment.json" "$TEMP_DIR/redis-service.json" "$TEMP_DIR/web-deployment.json" "$TEMP_DIR/web-service.json" # Behavior with -o / convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -o $TEMP_DIR/output_file -j" "$TEMP_DIR/output_file" # Behavior with -o / dst=$TEMP_DIR/output_dir/ convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -o $dst -j" "${dst}redis-deployment.json" "${dst}redis-service.json" "${dst}web-deployment.json" "${dst}web-service.json" # Behavior with -o / convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/redis-example/compose.yaml convert -o $TEMP_DIR/output_dir2/output_file -j" "$TEMP_DIR/output_dir2/output_file" #TEST the pvc-request-size command parameter convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/pvc-request-size/compose.yaml convert -o $TEMP_DIR/output_dir2/output-k8s.json -j --pvc-request-size=300Mi" "$TEMP_DIR/output_dir2/output-k8s.json" convert::check_artifacts_generated "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/pvc-request-size/compose.yaml convert -o $TEMP_DIR/output_dir2/output-os.json -j --pvc-request-size=300Mi" "$TEMP_DIR/output_dir2/output-os.json" ###### # Test the path of build image (requires docker or podman) if [ -n "$CONTAINER_RUNTIME" ]; then echo "Container runtime detected: $CONTAINER_RUNTIME - running build tests" # Test build v2 absolute compose file convert::check_artifacts_generated "kompose --build local $BUILD_CMD_FLAGS -f $KOMPOSE_ROOT/script/test/fixtures/buildconfig/compose.yaml convert -o $TEMP_DIR/output_file" "$TEMP_DIR/output_file" # Test build v2 relative compose file relative_path=$($realpath_cmd --relative-to="$PWD" "$KOMPOSE_ROOT/script/test/fixtures/buildconfig/compose.yaml") convert::check_artifacts_generated "kompose --build local $BUILD_CMD_FLAGS -f $relative_path convert -o $TEMP_DIR/output_file" "$TEMP_DIR/output_file" # Test build v3 absolute compose file with context convert::check_artifacts_generated "kompose --build local $BUILD_CMD_FLAGS -f $KOMPOSE_ROOT/script/test/fixtures/buildconfig/compose-v3.yaml convert -o $TEMP_DIR/output_file" "$TEMP_DIR/output_file" # Test build v3 relative compose file with context relative_path=$($realpath_cmd --relative-to="$PWD" "$KOMPOSE_ROOT/script/test/fixtures/buildconfig/compose-v3.yaml") convert::check_artifacts_generated "kompose --build local $BUILD_CMD_FLAGS -f $relative_path convert -o $TEMP_DIR/output_file" "$TEMP_DIR/output_file" # ##### # Test the build config with push image # see tests_push_image.sh for local push test # Should warn when push image disabled cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/buildconfig/compose-build-no-image.yaml -o $TEMP_DIR/output_file convert --build=local $BUILD_CMD_FLAGS --push-image-registry=whatever" convert::expect_warning "$cmd" "Push image registry 'whatever' is specified but push image is disabled, skipping pushing to repository" else echo "SKIP: Build tests require docker or podman (neither is running)" fi #TEST the kompose.volume.storage-class-name label convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/compose.yaml convert -o $TEMP_DIR/output-k8s.yaml" "$TEMP_DIR/output-k8s.yaml" convert::check_artifacts_generated "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/compose.yaml convert -o $TEMP_DIR/output-os.yaml -j" "$TEMP_DIR/output-os.yaml" # TEST the windows volume # windows host path to windows container k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # # TEST the placement # should convert placement to affinity k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/compose-placement.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/compose-placement.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/deploy/placement/output-placement-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/deploy/placement/output-placement-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # test configmap volume k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/compose.yaml convert --stdout --with-kompose-annotation=false --volumes=configMap" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/compose.yaml convert --stdout --with-kompose-annotation=false --volumes=configMap" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # test configmap volume using service label k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/compose-withlabel.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/compose-withlabel.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-k8s-withlabel.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-os-withlabel.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # test configmap pod generation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-pod/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-pod/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-pod/output-os.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/configmap-pod/compose.yaml convert --stdout --with-kompose-annotation=false" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test that emptyDir works k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/compose.yaml convert --with-kompose-annotation=false --stdout --volumes emptyDir" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/compose.yaml convert --with-kompose-annotation=false --stdout --volumes emptyDir" os_output="$KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test that emptyvols works k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/compose.yaml convert --with-kompose-annotation=false --stdout --emptyvols" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-k8s.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/compose.yaml convert --with-kompose-annotation=false --stdout --emptyvols" os_output="$KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # test service expose k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/expose/compose.yaml convert --stdout --with-kompose-annotation=false" ocp_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/expose/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/expose/output-k8s.yaml" ocp_output="$KOMPOSE_ROOT/script/test/fixtures/expose/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$ocp_cmd" "$ocp_output" || exit 1 # test service group by volume, not support openshift for now k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/service-group/compose.yaml convert --stdout --with-kompose-annotation=false --service-group-mode=volume --service-group-name=librenms-dispatcher" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/service-group/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 # test merge multiple compose files k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/first.yaml -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/second.yaml convert --stdout --with-kompose-annotation=false" ocp_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/first.yaml -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/second.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-k8s.yaml" ocp_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$ocp_cmd" "$ocp_output" || exit 1 # test health check k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/compose-healthcheck.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/compose-healthcheck.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # test statefulset k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/statefulset/compose.yaml convert --stdout --with-kompose-annotation=false --controller statefulset" ocp_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/statefulset/compose.yaml convert --stdout --with-kompose-annotation=false --controller statefulset" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/statefulset/output-k8s.yaml" ocp_output="$KOMPOSE_ROOT/script/test/fixtures/statefulset/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$ocp_cmd" "$ocp_output" || exit 1 # test cronjob k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/cronjob/compose.yaml convert --stdout --with-kompose-annotation=false" ocp_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/cronjob/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/cronjob/output-k8s.yaml" ocp_output="$KOMPOSE_ROOT/script/test/fixtures/cronjob/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$ocp_cmd" "$ocp_output" || exit 1 # test specifying volume type using service label k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test environment variables interpolation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test load .env file by default k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/env-dotenv/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/env-dotenv/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/env-dotenv/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/env-dotenv/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test single file output feature k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/single-file-output/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/single-file-output/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 # Test host port and protocol feature k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test external traffic policy feature with valid configuration, warning is coming from the network policy. k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/compose-v1.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/compose-v1.yaml convert --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v1.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test external traffic policy feature with warning, because we have nodeport with external traffic policy k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/compose-v2.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/compose-v2.yaml convert --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v2.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test Pod security context fs group k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/fsgroup/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-k8s.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/fsgroup/compose.yaml convert --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test support for compose.yaml file k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-file-support/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/compose-file-support/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # Test support for compose env interpolation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-env-interpolation/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/compose-env-interpolation/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # Test support for compose env without interpolation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-env-no-interpolation/compose.yaml convert --stdout --no-interpolate --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/compose-env-no-interpolation/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # Test configmap generated by env_file with variable interpolation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/envfile-interpolation/compose.yaml convert --stdout --with-kompose-annotation=false" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/envfile-interpolation/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/envfile-interpolation/output-k8s.yaml" os_output="$KOMPOSE_ROOT/script/test/fixtures/envfile-interpolation/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test support for subpath volume k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/vols-subpath/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/vols-subpath/output-k8s.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/vols-subpath/compose.yaml convert --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/vols-subpath/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test support for network policies generation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/network-policies/compose.yaml convert --generate-network-policies --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/network-policies/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 # Test support for namespace generation k8s_cmd="kompose -f ./script/test/fixtures/namespace/compose.yaml convert --stdout --with-kompose-annotation=false -n web" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/namespace/output-k8s.yaml" os_cmd="kompose -f ./script/test/fixtures/namespace/compose.yaml convert --stdout --with-kompose-annotation=false -n web --provider openshift" os_output="$KOMPOSE_ROOT/script/test/fixtures/namespace/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test support for read only root fs k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/read-only/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/read-only/output-k8s.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/read-only/compose.yaml convert --stdout --with-kompose-annotation=false --provider openshift" os_output="$KOMPOSE_ROOT/script/test/fixtures/read-only/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test env_file support k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/env/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/env/output-k8s.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/env/compose.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/env/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # disabled until FormatEnvName can take into account conflicting/duplicate file names # Test env_file support for multiple env_file with the same name from different dirs # k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/env-multiple/compose.yaml convert --stdout --with-kompose-annotation=false" # k8s_output="$KOMPOSE_ROOT/script/test/fixtures/env-multiple/output-k8s.yaml" # os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/env-multiple/compose.yaml convert --provider openshift --stdout --with-kompose-annotation=false" # os_output="$KOMPOSE_ROOT/script/test/fixtures/env-multiple/output-os.yaml" # convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test that if we have no profile a warning should raised cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/no-profile-warning/compose.yaml convert" convert::expect_warning "$cmd" "No service selected. The profile specified in services of your compose yaml may not exist." || exit 1 # Test COMPOSE_FILE env variable is honored export COMPOSE_FILE="$KOMPOSE_ROOT/script/test/fixtures/compose-file-env-variable/compose.yaml $KOMPOSE_ROOT/script/test/fixtures/compose-file-env-variable/alternative-compose.yaml" k8s_cmd="kompose convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/compose-file-env-variable/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # Test resources names lowercase k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-k8s.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/compose.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test resources to generate initcontainer k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/initcontainer/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/initcontainer/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 # Test HPA k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/hpa/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/hpa/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 #Test auto configmaps from files/dir k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-1.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-k8s-1.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-1.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-os-1.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 #Test auto configmaps from files/dir k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-2.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-k8s-2.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-2.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-os-2.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 #Test auto configmaps from files/dir k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-3.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-k8s-3.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/compose-3.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/configmap-file-configs/output-os-3.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success_and_warning "$os_cmd" "$os_output" || exit 1 # Test network_mode: service: k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/network-mode-service/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/network-mode-service/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 # Test env var with status k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/envvars-with-status/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-with-status/output-k8s.yaml" os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/envvars-with-status/compose.yaml convert --provider openshift --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-with-status/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 # Test label in compose.yaml appears in the output annotation k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/label/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/label/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # Test deploy.labels in compose.yaml appears in the output k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/deploy/labels/compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/deploy/labels/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 # TEST the security context conversion in service groups k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/security-contexts/compose.yaml convert --stdout --with-kompose-annotation=false --service-group-mode label" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/security-contexts/output-k8s.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 ================================================ FILE: script/test/cmd/tests_push_image.sh ================================================ #!/bin/bash # Copyright 2017 The Kubernetes Authors All rights reserved. # # 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 pe#rmissions and # limitations under the License. # Here are tests for pushing image on authentication and custom registry. # These tests only work on local for authentication inconvenient. # Prerequisites: # * `docker.io` account and docker login successfully # * custom registry which login as well. Or a local hosted registry. # * `jq` installed # Variables TEMP_DIR="/tmp/kompose" mkdir -p $TEMP_DIR mkdir -p "$TEMP_DIR/build" DOCKER_LOGIN_USER="lexcao" # TODO change this to your account for pushing to docker.io COMPOSE_FILE="$TEMP_DIR/docker-compose-push.yml" BUILD_FILE="$TEMP_DIR/build/Dockerfile" CUSTOM_REGISTRY="localhost:5000" # TODO change this to your local registry # Custom compose file based on parameter build_file_content="FROM busybox RUN touch /test" echo "$build_file_content" >> "$BUILD_FILE" compose_file_content="version: \"2\" services: foo: build: \"./build\" image: docker.io/$DOCKER_LOGIN_USER/foobar" echo "$compose_file_content" >> "$COMPOSE_FILE" # Some helper functions function get_docker_hub_tag() { local image=$1 local tag=$2 curl "https://hub.docker.com/v2/repositories/$image/tags/$tag/" | jq } function get_custom_registry_tag() { local image=$1 local tag=$2 curl "http://$CUSTOM_REGISTRY/v2/$image/manifests/$tag" -I } ################################################################################### cmd="kompose convert -f $COMPOSE_FILE -o $TEMP_DIR/output_file --build=local --push-image=true" printf "Push image without custom registry default to docker.io\n" echo "executing cmd '$cmd'" $cmd printf "\nVerify push success...\n" get_docker_hub_tag "$DOCKER_LOGIN_USER/foobar" "latest" ####### printf "\nPush image with custom registry\n" cmd="$cmd --push-image-registry=$CUSTOM_REGISTRY" echo "executing cmd '$cmd'" $cmd #kompose convert -f "$COMPOSE_FILE" -o "$TEMP_DIR/output_file" --build=local --push-image=true printf "\nVerify push success...\n" get_custom_registry_tag "$DOCKER_LOGIN_USER/foobar" "latest" # Clean resource rm -rf $TEMP_DIR ================================================ FILE: script/test/cmd/update-e2e.sh ================================================ #!/bin/bash # HOW TO USE: # In your kompose director execute: UPDATE_OS=true UPDATE_K8S=false ./update-e2e.sh make bin # Feel free to update this variable to match your current FS export KOMPOSE_ROOT=$HOME/projects/kompose if $UPDATE_K8S ; then $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/v2.0/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/v2.0/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/v3.0/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/v3.0/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/docker-compose-placement.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/output-placement-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/docker-compose.yml convert --stdout --with-kompose-annotation=false --volumes=configMap > $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/docker-compose-withlabel.yml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-k8s-withlabel.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/docker-compose.yml convert --with-kompose-annotation=false --stdout --volumes emptyDir > $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/docker-compose.yml convert --with-kompose-annotation=false --stdout --emptyvols > $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/expose/compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/expose/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/service-group/compose.yaml convert --stdout --with-kompose-annotation=false --service-group-mode=volume >$KOMPOSE_ROOT/script/test/fixtures/service-group/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/first.yaml -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/second.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml convert --stdout --service-group-mode=label --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/statefulset/docker-compose.yaml convert --stdout --with-kompose-annotation=false --controller statefulset > $KOMPOSE_ROOT/script/test/fixtures/statefulset/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/single-file-output/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/single-file-output/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-file-support/compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/compose-file-support/output-k8s.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/env/docker-compose.yml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/env/output-k8s.yaml fi if $UPDATE_OS ; then $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/v2.0/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/v2.0/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/v3.0/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/v3.0/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/docker-compose-placement.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/deploy/placement/output-placement-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/docker-compose.yml convert --stdout --with-kompose-annotation=false --volumes=configMap > $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/docker-compose-withlabel.yml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/configmap-volume/output-os-withlabel.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/docker-compose.yml convert --with-kompose-annotation=false --stdout --volumes emptyDir > $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/docker-compose.yml convert --with-kompose-annotation=false --stdout --emptyvols > $KOMPOSE_ROOT/script/test/fixtures/change-in-volume/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/expose/compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/expose/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/first.yaml -f $KOMPOSE_ROOT/script/test/fixtures/multiple-files/second.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml convert --stdout --service-group-mode=label --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/statefulset/docker-compose.yaml convert --stdout --with-kompose-annotation=false --controller statefulset > $KOMPOSE_ROOT/script/test/fixtures/statefulset/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/docker-compose.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-os.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v1.yaml $KOMPOSE_ROOT/kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false > $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v2.yaml $KOMPOSE_ROOT/kompose -f $KOMPOSE_ROOT/script/test/fixtures/env/docker-compose.yml convert --stdout --with-kompose-annotation=false --provider openshift > $KOMPOSE_ROOT/script/test/fixtures/env/output-os.yaml fi ================================================ FILE: script/test/fixtures/buildargs/README.md ================================================ ## Compose Buildargs ### Usage The simplest thing to do: ```bash export $(cat envs) ``` To customize the values edit `envs` file. ================================================ FILE: script/test/fixtures/buildargs/build/Dockerfile ================================================ FROM busybox RUN touch /test ================================================ FILE: script/test/fixtures/buildargs/compose.yaml ================================================ services: foo: build: context: "./build" args: NAME: web command: "sleep 3600" foo1: build: context: "./build" args: - NAME=web - foo command: "sleep 3600" ================================================ FILE: script/test/fixtures/buildargs/envs ================================================ foo=bar ================================================ FILE: script/test/fixtures/buildargs/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "foo" ], "from": { "kind": "ImageStreamTag", "name": "foo:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "foo" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "name": "foo", "image": " ", "args": [ "sleep", "3600" ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "foo" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/buildargs/build/" }, "strategy": { "type": "Docker", "dockerStrategy": { "env": [ { "name": "NAME", "value": "web" } ] } }, "output": { "to": { "kind": "ImageStreamTag", "name": "foo:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "foo1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "foo1" ], "from": { "kind": "ImageStreamTag", "name": "foo1:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "foo1" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo1" } }, "spec": { "containers": [ { "name": "foo1", "image": " ", "args": [ "sleep", "3600" ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "foo1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo1" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "foo1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "foo1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/buildargs/build/" }, "strategy": { "type": "Docker", "dockerStrategy": { "env": [ { "name": "NAME", "value": "web" }, { "name": "foo", "value": "bar" } ] } }, "output": { "to": { "kind": "ImageStreamTag", "name": "foo1:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } } ] } ================================================ FILE: script/test/fixtures/buildconfig/build/Dockerfile ================================================ FROM busybox RUN touch /test ================================================ FILE: script/test/fixtures/buildconfig/compose-build-no-image.yaml ================================================ services: foo: build: "./build" ================================================ FILE: script/test/fixtures/buildconfig/compose-dockerfile.yaml ================================================ services: foo: build: context: . dockerfile: build/Dockerfile image: docker.io/cdrage/foobar ================================================ FILE: script/test/fixtures/buildconfig/compose-v3.yaml ================================================ services: foo: build: context: ./build dockerfile: Dockerfile ================================================ FILE: script/test/fixtures/buildconfig/compose.yaml ================================================ services: foo: build: "./build" image: docker.io/cdrage/foobar ================================================ FILE: script/test/fixtures/change-in-volume/compose.yaml ================================================ services: web: image: flask_web command: python app.py ports: - "5000:5000" volumes: - code_volume:/code links: - redis redis: image: redis labels: kompose.service.type: headless volumes: code_volume: ================================================ FILE: script/test/fixtures/change-in-volume/output-k8s-empty-vols-template.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis name: redis restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - args: - python - app.py image: flask_web name: web ports: - containerPort: 5000 protocol: TCP volumeMounts: - mountPath: /code name: code-volume restartPolicy: Always volumes: - name: code-volume ================================================ FILE: script/test/fixtures/change-in-volume/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis name: redis restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - args: - python - app.py image: flask_web name: web ports: - containerPort: 5000 protocol: TCP volumeMounts: - mountPath: /code name: code-volume restartPolicy: Always volumes: - name: code-volume ================================================ FILE: script/test/fixtures/change-in-volume/output-os-empty-vols-template.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' name: redis restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - args: - python - app.py image: ' ' name: web ports: - containerPort: 5000 protocol: TCP volumeMounts: - mountPath: /code name: code-volume restartPolicy: Always volumes: - name: code-volume test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: flask_web name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/change-in-volume/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' name: redis restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - args: - python - app.py image: ' ' name: web ports: - containerPort: 5000 protocol: TCP volumeMounts: - mountPath: /code name: code-volume restartPolicy: Always volumes: - name: code-volume test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: flask_web name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/compose-env-interpolation/compose.yaml ================================================ services: foo: labels: kompose.image-pull-policy: "${IMAGE_PULL_POLICY:-IfNotPresent}" build: . ports: - 80:80 ================================================ FILE: script/test/fixtures/compose-env-interpolation/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: foo name: foo spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: foo --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: foo name: foo spec: replicas: 1 selector: matchLabels: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - image: foo imagePullPolicy: IfNotPresent name: foo ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/compose-env-no-interpolation/compose.yaml ================================================ services: foo: image: ${DOCKER_REGISTRY-}foo:${IMAGE_TAG:-latest} build: . environment: - VERSION=${IMAGE_TAG:-latest} ports: - 80:80 ================================================ FILE: script/test/fixtures/compose-env-no-interpolation/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: foo name: foo spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: foo --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: foo name: foo spec: replicas: 1 selector: matchLabels: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - env: - name: VERSION value: ${IMAGE_TAG:-latest} image: ${DOCKER_REGISTRY-}foo:${IMAGE_TAG:-latest} name: foo ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/compose-file-env-variable/alternative-compose.yaml ================================================ services: alpine: image: alpine ports: - 80:80 ================================================ FILE: script/test/fixtures/compose-file-env-variable/compose.yaml ================================================ services: debian: image: debian ports: - 80:80 ================================================ FILE: script/test/fixtures/compose-file-env-variable/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: alpine name: alpine spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: alpine --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: debian name: debian spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: debian --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: alpine name: alpine spec: replicas: 1 selector: matchLabels: io.kompose.service: alpine template: metadata: labels: io.kompose.service: alpine spec: containers: - image: alpine name: alpine ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: debian name: debian spec: replicas: 1 selector: matchLabels: io.kompose.service: debian template: metadata: labels: io.kompose.service: debian spec: containers: - image: debian name: debian ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/compose-file-support/compose.yaml ================================================ services: web: image: nginx:latest ports: - "80:80" ================================================ FILE: script/test/fixtures/compose-file-support/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: web --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx:latest name: web ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-config-long-warning.yaml ================================================ services: redis: image: redis:latest deploy: replicas: 1 configs: - source: my_config target: /redis_config uid: '103' gid: '103' mode: 0440 configs: my_config: file: ./my_config.txt my_other_config: external: true ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-config-long.yaml ================================================ services: redis: image: redis:latest deploy: replicas: 1 configs: - source: my_config target: /redis_config uid: '103' gid: '103' mode: 0440 configs: my_config: file: ./my_config.txt ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-config-short-warning.yaml ================================================ services: redis: image: redis:latest deploy: replicas: 1 configs: - my_config - my_other_config configs: my_config: file: ./my_config.txt my_other_config: external: true ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-config-short.yaml ================================================ services: redis: image: redis:latest deploy: replicas: 1 configs: - my_config configs: my_config: file: ./my_config.txt ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-endpoint-mode-1.yaml ================================================ services: wordpress: image: wordpress ports: - "8080:80" deploy: mode: replicated replicas: 2 endpoint_mode: vip ================================================ FILE: script/test/fixtures/compose-v3.3-test/compose-endpoint-mode-2.yaml ================================================ services: wordpress: image: wordpress ports: - "8080:80" deploy: mode: replicated replicas: 2 endpoint_mode: dnsrr ================================================ FILE: script/test/fixtures/compose-v3.3-test/my_config.txt ================================================ aaaa ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-config-long-warning.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/redis_config", "name": "my-config", "subPath": "redis_config" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "defaultMode": 288, "items": [ { "key": "my_config.txt", "path": "redis_config" } ], "name": "my-config" }, "name": "my-config" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-config-long.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/redis_config", "name": "my-config", "subPath": "redis_config" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "defaultMode": 288, "items": [ { "key": "my_config.txt", "path": "redis_config" } ], "name": "my-config" }, "name": "my-config" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-config-short-warning.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/my_config", "name": "my-config", "subPath": "my_config" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "items": [ { "key": "my_config.txt", "path": "my_config" } ], "name": "my-config" }, "name": "my-config" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-config-short.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/my_config", "name": "my-config", "subPath": "my_config" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "items": [ { "key": "my_config.txt", "path": "my_config" } ], "name": "my-config" }, "name": "my-config" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-endpoint-mode-1.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 80 } ], "selector": { "io.kompose.service": "wordpress" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "name": "wordpress" }, "spec": { "replicas": 2, "selector": { "matchLabels": { "io.kompose.service": "wordpress" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "containers": [ { "image": "wordpress", "imagePullPolicy": "", "name": "wordpress", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-k8s-endpoint-mode-2.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 80 } ], "selector": { "io.kompose.service": "wordpress" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "name": "wordpress" }, "spec": { "replicas": 2, "selector": { "matchLabels": { "io.kompose.service": "wordpress" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "containers": [ { "image": "wordpress", "imagePullPolicy": "", "name": "wordpress", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-os-config-long.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "my-config", "configMap": { "name": "my-config", "items": [ { "key": "my_config.txt", "path": "redis_config" } ], "defaultMode": 288 } } ], "containers": [ { "name": "redis", "image": " ", "resources": {}, "volumeMounts": [ { "name": "my-config", "mountPath": "/redis_config", "subPath": "redis_config" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-os-config-short.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "my-config", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "data": { "my_config.txt": "aaaa" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "my-config", "configMap": { "name": "my-config", "items": [ { "key": "my_config.txt", "path": "my_config" } ] } } ], "containers": [ { "name": "redis", "image": " ", "resources": {}, "volumeMounts": [ { "name": "my-config", "mountPath": "/my_config", "subPath": "my_config" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/compose-v3.3-test/output-os-mode-1.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 80 } ], "selector": { "io.kompose.service": "wordpress" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "wordpress" ], "from": { "kind": "ImageStreamTag", "name": "wordpress:latest" } } } ], "replicas": 2, "test": false, "selector": { "io.kompose.service": "wordpress" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "containers": [ { "name": "wordpress", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "wordpress" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/configmap/bar.env ================================================ # Multi-line test FOO=BAR BAR=FOO ================================================ FILE: script/test/fixtures/configmap/compose.yaml ================================================ services: redis: image: 'bitnami/redis:latest' environment: - ALLOW_EMPTY_PASSWORD=no # Env file will override environment / warn! env_file: - "foo.env" - bar.env labels: kompose.service.type: nodeport ports: - '6379:6379' ================================================ FILE: script/test/fixtures/configmap/foo.env ================================================ # Test comment! ALLOW_EMPTY_PASSWORD=yes ================================================ FILE: script/test/fixtures/configmap/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "valueFrom": { "configMapKeyRef": { "key": "ALLOW_EMPTY_PASSWORD", "name": "foo-env" } } }, { "name": "BAR", "valueFrom": { "configMapKeyRef": { "key": "BAR", "name": "bar-env" } } }, { "name": "FOO", "valueFrom": { "configMapKeyRef": { "key": "FOO", "name": "bar-env" } } } ], "image": "bitnami/redis:latest", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "foo-env", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-foo-env" } }, "data": { "ALLOW_EMPTY_PASSWORD": "yes" } }, { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "bar-env", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-bar-env" } }, "data": { "BAR": "FOO", "FOO": "BAR" } } ] } ================================================ FILE: script/test/fixtures/configmap-file-configs/auth.txt ================================================ content from file auth.txt ================================================ FILE: script/test/fixtures/configmap-file-configs/certs/cert1.pem ================================================ content of file cert1.pem ================================================ FILE: script/test/fixtures/configmap-file-configs/certs-level1/certs-level2/certs-level3/cert2.pem ================================================ content from file cert2.pem ================================================ FILE: script/test/fixtures/configmap-file-configs/compose-1.yaml ================================================ services: busy: image: busybox ports: - "8081:8080" - "8026:8025" volumes: - ./certs:/certs - ./auth.txt:/auth.txt - ./users.php:/users.php:ro command: [ "/bin/sh", "-c", "cat /auth.txt /users.php /certs/cert1.pem" ] ================================================ FILE: script/test/fixtures/configmap-file-configs/compose-2.yaml ================================================ services: busy: image: busybox ports: - "8081:8080" - "8026:8025" volumes: - ./certs:/certs - ./certs-level1/certs-level2/certs-level3/cert2.pem:/certs/cert2.pem - ./auth.txt:/auth.txt - ./users.php:/users.php:ro command: [ "/bin/sh", "-c", "cat /auth.txt /users.php /certs/cert1.pem /certs/cert2.pem" ] ================================================ FILE: script/test/fixtures/configmap-file-configs/compose-3.yaml ================================================ services: busy: image: busybox ports: - "8081:8080" - "8026:8025" volumes: - /tmp:/tmp:ro ================================================ FILE: script/test/fixtures/configmap-file-configs/output-k8s-1.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: matchLabels: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - args: - /bin/sh - -c - cat /auth.txt /users.php /certs/cert1.pem image: busybox name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP volumeMounts: - mountPath: /certs name: busy-cm0 - mountPath: /auth.txt name: busy-cm1 subPath: auth.txt - mountPath: /users.php name: busy-cm2 subPath: users.php readOnly: true restartPolicy: Always volumes: - configMap: name: busy-cm0 name: busy-cm0 - configMap: items: - key: auth.txt path: auth.txt name: busy-cm1 name: busy-cm1 - configMap: items: - key: users.php path: users.php name: busy-cm2 name: busy-cm2 --- apiVersion: v1 data: cert1.pem: | content of file cert1.pem kind: ConfigMap metadata: labels: io.kompose.service: busy name: busy-cm0 --- apiVersion: v1 data: auth.txt: | content from file auth.txt kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm1 --- apiVersion: v1 data: users.php: | content from file users.php kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm2 ================================================ FILE: script/test/fixtures/configmap-file-configs/output-k8s-2.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: matchLabels: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - args: - /bin/sh - -c - cat /auth.txt /users.php /certs/cert1.pem /certs/cert2.pem image: busybox name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP volumeMounts: - mountPath: /certs name: busy-cm0 - mountPath: /certs/cert2.pem name: busy-cm1 subPath: cert2.pem - mountPath: /auth.txt name: busy-cm2 subPath: auth.txt - mountPath: /users.php name: busy-cm3 readOnly: true subPath: users.php restartPolicy: Always volumes: - configMap: name: busy-cm0 name: busy-cm0 - configMap: items: - key: cert2.pem path: cert2.pem name: busy-cm1 name: busy-cm1 - configMap: items: - key: auth.txt path: auth.txt name: busy-cm2 name: busy-cm2 - configMap: items: - key: users.php path: users.php name: busy-cm3 name: busy-cm3 --- apiVersion: v1 data: cert1.pem: | content of file cert1.pem kind: ConfigMap metadata: labels: io.kompose.service: busy name: busy-cm0 --- apiVersion: v1 data: cert2.pem: content from file cert2.pem kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm1 --- apiVersion: v1 data: auth.txt: | content from file auth.txt kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm2 --- apiVersion: v1 data: users.php: | content from file users.php kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm3 ================================================ FILE: script/test/fixtures/configmap-file-configs/output-k8s-3.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: matchLabels: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - image: busybox name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/configmap-file-configs/output-os-1.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - args: - /bin/sh - -c - cat /auth.txt /users.php /certs/cert1.pem image: ' ' name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP volumeMounts: - mountPath: /certs name: busy-cm0 - mountPath: /auth.txt name: busy-cm1 subPath: auth.txt - mountPath: /users.php name: busy-cm2 readOnly: true subPath: users.php restartPolicy: Always volumes: - configMap: name: busy-cm0 name: busy-cm0 - configMap: items: - key: auth.txt path: auth.txt name: busy-cm1 name: busy-cm1 - configMap: items: - key: users.php path: users.php name: busy-cm2 name: busy-cm2 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - busy from: kind: ImageStreamTag name: busy:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: busy name: busy spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: busybox name: latest referencePolicy: type: "" --- apiVersion: v1 data: cert1.pem: | content of file cert1.pem kind: ConfigMap metadata: labels: io.kompose.service: busy name: busy-cm0 --- apiVersion: v1 data: auth.txt: | content from file auth.txt kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm1 --- apiVersion: v1 data: users.php: | content from file users.php kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm2 ================================================ FILE: script/test/fixtures/configmap-file-configs/output-os-2.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - args: - /bin/sh - -c - cat /auth.txt /users.php /certs/cert1.pem /certs/cert2.pem image: ' ' name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP volumeMounts: - mountPath: /certs name: busy-cm0 - mountPath: /certs/cert2.pem name: busy-cm1 subPath: cert2.pem - mountPath: /auth.txt name: busy-cm2 subPath: auth.txt - mountPath: /users.php name: busy-cm3 readOnly: true subPath: users.php restartPolicy: Always volumes: - configMap: name: busy-cm0 name: busy-cm0 - configMap: items: - key: cert2.pem path: cert2.pem name: busy-cm1 name: busy-cm1 - configMap: items: - key: auth.txt path: auth.txt name: busy-cm2 name: busy-cm2 - configMap: items: - key: users.php path: users.php name: busy-cm3 name: busy-cm3 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - busy from: kind: ImageStreamTag name: busy:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: busy name: busy spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: busybox name: latest referencePolicy: type: "" --- apiVersion: v1 data: cert1.pem: | content of file cert1.pem kind: ConfigMap metadata: labels: io.kompose.service: busy name: busy-cm0 --- apiVersion: v1 data: cert2.pem: content from file cert2.pem kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm1 --- apiVersion: v1 data: auth.txt: | content from file auth.txt kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm2 --- apiVersion: v1 data: users.php: | content from file users.php kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: busy name: busy-cm3 ================================================ FILE: script/test/fixtures/configmap-file-configs/output-os-3.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: busy name: busy spec: ports: - name: "8081" port: 8081 targetPort: 8080 - name: "8026" port: 8026 targetPort: 8025 selector: io.kompose.service: busy --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: busy name: busy spec: replicas: 1 selector: io.kompose.service: busy strategy: type: Recreate template: metadata: labels: io.kompose.service: busy spec: containers: - image: ' ' name: busy ports: - containerPort: 8080 protocol: TCP - containerPort: 8025 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - busy from: kind: ImageStreamTag name: busy:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: busy name: busy spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: busybox name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/configmap-file-configs/users.php ================================================ content from file users.php ================================================ FILE: script/test/fixtures/configmap-pod/bar.env ================================================ # Multi-line test FOO=BAR BAR=FOO ================================================ FILE: script/test/fixtures/configmap-pod/compose.yaml ================================================ services: redis: image: 'bitnami/redis:latest' environment: - ALLOW_EMPTY_PASSWORD=no # Env file will override environment / warn! env_file: - "foo.env" - bar.env labels: kompose.service.type: nodeport ports: - '6379:6379' restart: "no" ================================================ FILE: script/test/fixtures/configmap-pod/foo.env ================================================ # Test comment! ALLOW_EMPTY_PASSWORD=yes ================================================ FILE: script/test/fixtures/configmap-pod/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis type: NodePort --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: redis name: redis spec: containers: - envFrom: - configMapRef: name: foo-env - configMapRef: name: bar-env image: bitnami/redis:latest name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Never --- apiVersion: v1 data: ALLOW_EMPTY_PASSWORD: "yes" kind: ConfigMap metadata: labels: io.kompose.service: redis-foo-env name: foo-env --- apiVersion: v1 data: BAR: FOO FOO: BAR kind: ConfigMap metadata: labels: io.kompose.service: redis-bar-env name: bar-env ================================================ FILE: script/test/fixtures/configmap-pod/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis type: NodePort --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: redis name: redis spec: containers: - envFrom: - configMapRef: name: foo-env - configMapRef: name: bar-env image: bitnami/redis:latest name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Never --- apiVersion: v1 data: ALLOW_EMPTY_PASSWORD: "yes" kind: ConfigMap metadata: labels: io.kompose.service: redis-foo-env name: foo-env --- apiVersion: v1 data: BAR: FOO FOO: BAR kind: ConfigMap metadata: labels: io.kompose.service: redis-bar-env name: bar-env ================================================ FILE: script/test/fixtures/configmap-volume/compose-withlabel.yaml ================================================ services: web: image: nginx volumes: - ./tls:/etc/tls - ./tls/a.key:/etc/test-a-key.key labels: kompose.volume.type: configMap db: image: mysql volumes: - ./configs.tar:/data/configs.tar labels: kompose.volume.type: configMap ================================================ FILE: script/test/fixtures/configmap-volume/compose.yaml ================================================ services: web: image: nginx volumes: - ./tls:/etc/tls - ./tls/a.key:/etc/test-a-key.key db: image: mysql volumes: - ./configs.tar:/data/configs.tar ================================================ FILE: script/test/fixtures/configmap-volume/output-k8s-withlabel.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: mysql name: db volumeMounts: - mountPath: /data/configs.tar name: db-cm0 subPath: configs.tar restartPolicy: Always volumes: - configMap: items: - key: configs.tar path: configs.tar name: db-cm0 name: db-cm0 --- apiVersion: v1 binaryData: configs.tar: SDRzSUFId3pMVjhBQSsxV3pXc1RRUlFmQ3lMdVJRL2lRVkNHVmFsSVBtWm12N0tXRkFvRksxSU1UU29GVjhLWVRHc2tYK3h1WkV1SjlPaS80TkdiWjIrQ2wrSk4vQ3M4OTZ4WFo3K3lTWGFEc1NZcEpmdExabWJmNC9mbXZielpOeThsNm13eFdtZG12dFpwN3pjT0xEQjdJSVEwUllGQTh4Q3RLQUJHQkdKSnd4S1JzS0xKRUJGRVpCVkFadzZ4eE5DemJHcnlVTXl1MVdVbU0ra0Uzdk1uTzVYZDhvYXgyV25SUmh2dVdzd2N5WlgvVStCZ3ZTREFCWGpRcUJleHJoTkZ3YXFHQks3cGVScFoxeVFkUzdJZ0lkaXlHeTFXeElxdWFycFdJQ2lueWhqcC9Lc0w1LzBMVXZ3UGdxclB6OU5IV1AvSC9ObFdQNzF6MTdmZlRuK2hDT1AxajVBQ29ETFBvRUlzZWYySDUxOGE5SUZjdFU1dG1udGpkZG96OHNIem9jcnk1UHNmY1dIMC9JbWt5T245dndoTWMvOFRMZkgreHdVbHZmd3ZPc0w2bjMzVlJ3anIveGdrMy84eWtjZnJIeEgrL3cvTlBKSUVMSG45ZzhzM3I0QVZBTFpwRFQ0cnd6MFl3TldCcTN3UVBuN3l3ZVZMTjZiYmNxTlMyZkdmUEl1dmZPeU5VVllDL1hVQWJ0VTZyUnp0ZHBzczE2U1czYk5ZbmIrSzdHNnBISEIvOExFRndMV0kxNksxSmxlZWttelZKYnkzbjk3M3FYY2Uvbjc5NE9Qai91ZnZINm9uWDA1dW56VXB5NE40LzUvOVBYQ0cvcy9GdFA4dkFtbi9YMjZFOVQrLzd2L1gvbzlsTkY3L1dNVlMydjhYZ1JjQ2hFZUMyL0ZGeHhFZlFZd3lvZVRKN2lwT3BmSWVNKzY4Rmd3SERxWTFMR2RITmQ0MG9nL1VlY2NRTTlEd3RqVDQzdjZUVTNWOFJYL1l0ZWZidDh1dXIzUHF2WndoK2diUXA3c2ZPT1ExTXhYWkR3NW1qRVEyRHpHaVJ6dTdVVTNlUFRtZUFOeldjR0syc1p3RjgzaWdtU1RIM0hpVEhnNmJRY2ZMYldETEpXaTBvLzJQM0xrZitmTHlEWXR3ZGVoMEJoNVh4ODdmZjROODJYMkpQTEgvajV3WUswNmpOYnZSYWROWFRiYk5xTlV6V2VXd3l4S3NTRklBNGxBdUJ0a1hPYVV2dkR6dk1reVJJa1dLaGVNUENNUUc3Z0FjQUFBPQ== kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: db name: db-cm0 --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/configmap-volume/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: mysql name: db volumeMounts: - mountPath: /data/configs.tar name: db-cm0 subPath: configs.tar restartPolicy: Always volumes: - configMap: items: - key: configs.tar path: configs.tar name: db-cm0 name: db-cm0 --- apiVersion: v1 binaryData: configs.tar: SDRzSUFId3pMVjhBQSsxV3pXc1RRUlFmQ3lMdVJRL2lRVkNHVmFsSVBtWm12N0tXRkFvRksxSU1UU29GVjhLWVRHc2tYK3h1WkV1SjlPaS80TkdiWjIrQ2wrSk4vQ3M4OTZ4WFo3K3lTWGFEc1NZcEpmdExabWJmNC9mbXZielpOeThsNm13eFdtZG12dFpwN3pjT0xEQjdJSVEwUllGQTh4Q3RLQUJHQkdKSnd4S1JzS0xKRUJGRVpCVkFadzZ4eE5DemJHcnlVTXl1MVdVbU0ra0Uzdk1uTzVYZDhvYXgyV25SUmh2dVdzd2N5WlgvVStCZ3ZTREFCWGpRcUJleHJoTkZ3YXFHQks3cGVScFoxeVFkUzdJZ0lkaXlHeTFXeElxdWFycFdJQ2lueWhqcC9Lc0w1LzBMVXZ3UGdxclB6OU5IV1AvSC9ObFdQNzF6MTdmZlRuK2hDT1AxajVBQ29ETFBvRUlzZWYySDUxOGE5SUZjdFU1dG1udGpkZG96OHNIem9jcnk1UHNmY1dIMC9JbWt5T245dndoTWMvOFRMZkgreHdVbHZmd3ZPc0w2bjMzVlJ3anIveGdrMy84eWtjZnJIeEgrL3cvTlBKSUVMSG45ZzhzM3I0QVZBTFpwRFQ0cnd6MFl3TldCcTN3UVBuN3l3ZVZMTjZiYmNxTlMyZkdmUEl1dmZPeU5VVllDL1hVQWJ0VTZyUnp0ZHBzczE2U1czYk5ZbmIrSzdHNnBISEIvOExFRndMV0kxNksxSmxlZWttelZKYnkzbjk3M3FYY2Uvbjc5NE9Qai91ZnZINm9uWDA1dW56VXB5NE40LzUvOVBYQ0cvcy9GdFA4dkFtbi9YMjZFOVQrLzd2L1gvbzlsTkY3L1dNVlMydjhYZ1JjQ2hFZUMyL0ZGeHhFZlFZd3lvZVRKN2lwT3BmSWVNKzY4Rmd3SERxWTFMR2RITmQ0MG9nL1VlY2NRTTlEd3RqVDQzdjZUVTNWOFJYL1l0ZWZidDh1dXIzUHF2WndoK2diUXA3c2ZPT1ExTXhYWkR3NW1qRVEyRHpHaVJ6dTdVVTNlUFRtZUFOeldjR0syc1p3RjgzaWdtU1RIM0hpVEhnNmJRY2ZMYldETEpXaTBvLzJQM0xrZitmTHlEWXR3ZGVoMEJoNVh4ODdmZjROODJYMkpQTEgvajV3WUswNmpOYnZSYWROWFRiYk5xTlV6V2VXd3l4S3NTRklBNGxBdUJ0a1hPYVV2dkR6dk1reVJJa1dLaGVNUENNUUc3Z0FjQUFBPQ== kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: db name: db-cm0 --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/configmap-volume/output-os-withlabel.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: ' ' name: db volumeMounts: - mountPath: /data/configs.tar name: db-cm0 subPath: configs.tar restartPolicy: Always volumes: - configMap: items: - key: configs.tar path: configs.tar name: db-cm0 name: db-cm0 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - db from: kind: ImageStreamTag name: db:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: db name: db spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mysql name: latest referencePolicy: type: "" --- apiVersion: v1 binaryData: configs.tar: SDRzSUFId3pMVjhBQSsxV3pXc1RRUlFmQ3lMdVJRL2lRVkNHVmFsSVBtWm12N0tXRkFvRksxSU1UU29GVjhLWVRHc2tYK3h1WkV1SjlPaS80TkdiWjIrQ2wrSk4vQ3M4OTZ4WFo3K3lTWGFEc1NZcEpmdExabWJmNC9mbXZielpOeThsNm13eFdtZG12dFpwN3pjT0xEQjdJSVEwUllGQTh4Q3RLQUJHQkdKSnd4S1JzS0xKRUJGRVpCVkFadzZ4eE5DemJHcnlVTXl1MVdVbU0ra0Uzdk1uTzVYZDhvYXgyV25SUmh2dVdzd2N5WlgvVStCZ3ZTREFCWGpRcUJleHJoTkZ3YXFHQks3cGVScFoxeVFkUzdJZ0lkaXlHeTFXeElxdWFycFdJQ2lueWhqcC9Lc0w1LzBMVXZ3UGdxclB6OU5IV1AvSC9ObFdQNzF6MTdmZlRuK2hDT1AxajVBQ29ETFBvRUlzZWYySDUxOGE5SUZjdFU1dG1udGpkZG96OHNIem9jcnk1UHNmY1dIMC9JbWt5T245dndoTWMvOFRMZkgreHdVbHZmd3ZPc0w2bjMzVlJ3anIveGdrMy84eWtjZnJIeEgrL3cvTlBKSUVMSG45ZzhzM3I0QVZBTFpwRFQ0cnd6MFl3TldCcTN3UVBuN3l3ZVZMTjZiYmNxTlMyZkdmUEl1dmZPeU5VVllDL1hVQWJ0VTZyUnp0ZHBzczE2U1czYk5ZbmIrSzdHNnBISEIvOExFRndMV0kxNksxSmxlZWttelZKYnkzbjk3M3FYY2Uvbjc5NE9Qai91ZnZINm9uWDA1dW56VXB5NE40LzUvOVBYQ0cvcy9GdFA4dkFtbi9YMjZFOVQrLzd2L1gvbzlsTkY3L1dNVlMydjhYZ1JjQ2hFZUMyL0ZGeHhFZlFZd3lvZVRKN2lwT3BmSWVNKzY4Rmd3SERxWTFMR2RITmQ0MG9nL1VlY2NRTTlEd3RqVDQzdjZUVTNWOFJYL1l0ZWZidDh1dXIzUHF2WndoK2diUXA3c2ZPT1ExTXhYWkR3NW1qRVEyRHpHaVJ6dTdVVTNlUFRtZUFOeldjR0syc1p3RjgzaWdtU1RIM0hpVEhnNmJRY2ZMYldETEpXaTBvLzJQM0xrZitmTHlEWXR3ZGVoMEJoNVh4ODdmZjROODJYMkpQTEgvajV3WUswNmpOYnZSYWROWFRiYk5xTlV6V2VXd3l4S3NTRklBNGxBdUJ0a1hPYVV2dkR6dk1reVJJa1dLaGVNUENNUUc3Z0FjQUFBPQ== kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: db name: db-cm0 --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: ' ' name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx name: latest referencePolicy: type: "" --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/configmap-volume/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: ' ' name: db volumeMounts: - mountPath: /data/configs.tar name: db-cm0 subPath: configs.tar restartPolicy: Always volumes: - configMap: items: - key: configs.tar path: configs.tar name: db-cm0 name: db-cm0 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - db from: kind: ImageStreamTag name: db:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: db name: db spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mysql name: latest referencePolicy: type: "" --- apiVersion: v1 binaryData: configs.tar: SDRzSUFId3pMVjhBQSsxV3pXc1RRUlFmQ3lMdVJRL2lRVkNHVmFsSVBtWm12N0tXRkFvRksxSU1UU29GVjhLWVRHc2tYK3h1WkV1SjlPaS80TkdiWjIrQ2wrSk4vQ3M4OTZ4WFo3K3lTWGFEc1NZcEpmdExabWJmNC9mbXZielpOeThsNm13eFdtZG12dFpwN3pjT0xEQjdJSVEwUllGQTh4Q3RLQUJHQkdKSnd4S1JzS0xKRUJGRVpCVkFadzZ4eE5DemJHcnlVTXl1MVdVbU0ra0Uzdk1uTzVYZDhvYXgyV25SUmh2dVdzd2N5WlgvVStCZ3ZTREFCWGpRcUJleHJoTkZ3YXFHQks3cGVScFoxeVFkUzdJZ0lkaXlHeTFXeElxdWFycFdJQ2lueWhqcC9Lc0w1LzBMVXZ3UGdxclB6OU5IV1AvSC9ObFdQNzF6MTdmZlRuK2hDT1AxajVBQ29ETFBvRUlzZWYySDUxOGE5SUZjdFU1dG1udGpkZG96OHNIem9jcnk1UHNmY1dIMC9JbWt5T245dndoTWMvOFRMZkgreHdVbHZmd3ZPc0w2bjMzVlJ3anIveGdrMy84eWtjZnJIeEgrL3cvTlBKSUVMSG45ZzhzM3I0QVZBTFpwRFQ0cnd6MFl3TldCcTN3UVBuN3l3ZVZMTjZiYmNxTlMyZkdmUEl1dmZPeU5VVllDL1hVQWJ0VTZyUnp0ZHBzczE2U1czYk5ZbmIrSzdHNnBISEIvOExFRndMV0kxNksxSmxlZWttelZKYnkzbjk3M3FYY2Uvbjc5NE9Qai91ZnZINm9uWDA1dW56VXB5NE40LzUvOVBYQ0cvcy9GdFA4dkFtbi9YMjZFOVQrLzd2L1gvbzlsTkY3L1dNVlMydjhYZ1JjQ2hFZUMyL0ZGeHhFZlFZd3lvZVRKN2lwT3BmSWVNKzY4Rmd3SERxWTFMR2RITmQ0MG9nL1VlY2NRTTlEd3RqVDQzdjZUVTNWOFJYL1l0ZWZidDh1dXIzUHF2WndoK2diUXA3c2ZPT1ExTXhYWkR3NW1qRVEyRHpHaVJ6dTdVVTNlUFRtZUFOeldjR0syc1p3RjgzaWdtU1RIM0hpVEhnNmJRY2ZMYldETEpXaTBvLzJQM0xrZitmTHlEWXR3ZGVoMEJoNVh4ODdmZjROODJYMkpQTEgvajV3WUswNmpOYnZSYWROWFRiYk5xTlV6V2VXd3l4S3NTRklBNGxBdUJ0a1hPYVV2dkR6dk1reVJJa1dLaGVNUENNUUc3Z0FjQUFBPQ== kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: db name: db-cm0 --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: ' ' name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx name: latest referencePolicy: type: "" --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/configmap-volume/tls/a.crt ================================================ test-crt-data... ================================================ FILE: script/test/fixtures/configmap-volume/tls/a.key ================================================ test-key-data.... ================================================ FILE: script/test/fixtures/controller/compose-controller-label-v3.yaml ================================================ services: web: image: wordpress:4 environment: - WORDPRESS_DB_PASSWORD=password - WORDPRESS_AUTH_KEY=changeme - WORDPRESS_SECURE_AUTH_KEY=changeme - WORDPRESS_LOGGED_IN_KEY=changeme - WORDPRESS_NONCE_KEY=changeme - WORDPRESS_AUTH_SALT=changeme - WORDPRESS_SECURE_AUTH_SALT=changeme - WORDPRESS_LOGGED_IN_SALT=changeme - WORDPRESS_NONCE_SALT=changeme - WORDPRESS_NONCE_AA=changeme ports: - 80 depends_on: - mysql deploy: replicas: 3 labels: port: wordpress mysql: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=password labels: kompose.controller.type: daemonset ================================================ FILE: script/test/fixtures/controller/compose-controller-label.yaml ================================================ web: image: wordpress:4.5 ports: - '80' environment: WORDPRESS_AUTH_KEY: changeme WORDPRESS_SECURE_AUTH_KEY: changeme WORDPRESS_LOGGED_IN_KEY: changeme WORDPRESS_NONCE_KEY: changeme WORDPRESS_AUTH_SALT: changeme WORDPRESS_SECURE_AUTH_SALT: changeme WORDPRESS_LOGGED_IN_SALT: changeme WORDPRESS_NONCE_SALT: changeme WORDPRESS_NONCE_AA: changeme restart: always links: - 'db:mysql' db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password restart: always labels: project.logs: /var/log/mysql kompose.controller.type: daemonset ================================================ FILE: script/test/fixtures/controller/compose-global.yaml ================================================ services: worker: image: dockersamples/examplevotingapp_worker deploy: mode: global ================================================ FILE: script/test/fixtures/controller/compose.yaml ================================================ services: redis-master: image: registry.k8s.io/redis:e2e ports: - "6379" redis-replica: image: registry.k8s.io/redis-slave:v2 ports: - "6379" environment: - GET_HOSTS_FROM=dns frontend: image: registry.k8s.io/guestbook:v3 ports: - "80:80" environment: - GET_HOSTS_FROM=dns labels: kompose.service.type: LoadBalancer ================================================ FILE: script/test/fixtures/controller/output-k8s-controller-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.controller.type": "daemonset", "kompose.version": "%VERSION%", "project.logs": "/var/log/mysql" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "env": [ { "name": "MYSQL_ROOT_PASSWORD", "value": "password" } ], "image": "mysql:5.7", "imagePullPolicy": "", "name": "db", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "env": [ { "name": "WORDPRESS_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_AUTH_SALT", "value": "changeme" }, { "name": "WORDPRESS_LOGGED_IN_KEY", "value": "changeme" }, { "name": "WORDPRESS_LOGGED_IN_SALT", "value": "changeme" }, { "name": "WORDPRESS_NONCE_AA", "value": "changeme" }, { "name": "WORDPRESS_NONCE_KEY", "value": "changeme" }, { "name": "WORDPRESS_NONCE_SALT", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_SALT", "value": "changeme" } ], "image": "wordpress:4.5", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-controller-v3-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "port": "wordpress" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.controller.type": "daemonset", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" }, "name": "mysql" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" } }, "spec": { "containers": [ { "env": [ { "name": "MYSQL_ROOT_PASSWORD", "value": "password" } ], "image": "mysql:5.7", "imagePullPolicy": "", "name": "mysql", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "port": "wordpress" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 3, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "port": "wordpress" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "env": [ { "name": "WORDPRESS_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_AUTH_SALT", "value": "changeme" }, { "name": "WORDPRESS_DB_PASSWORD", "value": "password" }, { "name": "WORDPRESS_LOGGED_IN_KEY", "value": "changeme" }, { "name": "WORDPRESS_LOGGED_IN_SALT", "value": "changeme" }, { "name": "WORDPRESS_NONCE_AA", "value": "changeme" }, { "name": "WORDPRESS_NONCE_KEY", "value": "changeme" }, { "name": "WORDPRESS_NONCE_SALT", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_SALT", "value": "changeme" } ], "image": "wordpress:4", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-daemonset-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "name": "frontend" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "gcr.io/google-samples/gb-frontend:v4", "imagePullPolicy": "", "name": "frontend", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "name": "redis-replica" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "registry.k8s.io/redis-slave:v2", "imagePullPolicy": "", "name": "redis-replica", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-deployment-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "name": "frontend" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "frontend" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "gcr.io/google-samples/gb-frontend:v4", "imagePullPolicy": "", "name": "frontend", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-master" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "name": "redis-replica" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-replica" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "registry.k8s.io/redis-slave:v2", "imagePullPolicy": "", "name": "redis-replica", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-global-deployment-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "worker" }, "name": "worker" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "worker" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "worker" } }, "spec": { "containers": [ { "image": "dockersamples/examplevotingapp_worker", "imagePullPolicy": "", "name": "worker", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-global-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "worker" }, "name": "worker" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "worker" } }, "spec": { "containers": [ { "image": "dockersamples/examplevotingapp_worker", "imagePullPolicy": "", "name": "worker", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } } ] } ================================================ FILE: script/test/fixtures/controller/output-k8s-rc-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "kind": "ReplicationController", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "name": "frontend", "image": "gcr.io/google-samples/gb-frontend:v4", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": { "replicas": 0 } }, { "kind": "ReplicationController", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "name": "redis-master", "image": "registry.k8s.io/redis:e2e", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": { "replicas": 0 } }, { "kind": "ReplicationController", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "name": "redis-replica", "image": "registry.k8s.io/redis-slave:v2", "ports": [ { "containerPort": 6379 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": { "replicas": 0 } } ] } ================================================ FILE: script/test/fixtures/controller/output-os-controller-v3-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "port": "wordpress" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.controller.type": "daemonset", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" }, "name": "mysql" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" } }, "spec": { "containers": [ { "env": [ { "name": "MYSQL_ROOT_PASSWORD", "value": "password" } ], "image": "mysql:5.7", "imagePullPolicy": "", "name": "mysql", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "mysql", "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.controller.type": "daemonset", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "mysql" ], "from": { "kind": "ImageStreamTag", "name": "mysql:5.7" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "mysql" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" } }, "spec": { "containers": [ { "name": "mysql", "image": " ", "env": [ { "name": "MYSQL_ROOT_PASSWORD", "value": "password" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "mysql", "creationTimestamp": null, "labels": { "io.kompose.service": "mysql" } }, "spec": { "tags": [ { "name": "5.7", "annotations": null, "from": { "kind": "DockerImage", "name": "mysql:5.7" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "port": "wordpress" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:4" } } } ], "replicas": 3, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "WORDPRESS_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_AUTH_SALT", "value": "changeme" }, { "name": "WORDPRESS_DB_PASSWORD", "value": "password" }, { "name": "WORDPRESS_LOGGED_IN_KEY", "value": "changeme" }, { "name": "WORDPRESS_LOGGED_IN_SALT", "value": "changeme" }, { "name": "WORDPRESS_NONCE_AA", "value": "changeme" }, { "name": "WORDPRESS_NONCE_KEY", "value": "changeme" }, { "name": "WORDPRESS_NONCE_SALT", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_KEY", "value": "changeme" }, { "name": "WORDPRESS_SECURE_AUTH_SALT", "value": "changeme" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "4", "annotations": null, "from": { "kind": "DockerImage", "name": "wordpress:4" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/cronjob/compose.yaml ================================================ services: challenge: image: busybox:stable-glibc labels: kompose.cronjob.schedule: "* * * * *" kompose.cronjob.concurrency_policy: "Forbid" kompose.cronjob.backoff_limit: "0" command: - "bash" - "-c" - "echo hello from cron" restart: "no" ================================================ FILE: script/test/fixtures/cronjob/output-k8s.yaml ================================================ --- apiVersion: batch/v1 kind: CronJob metadata: labels: io.kompose.service: challenge name: challenge spec: concurrencyPolicy: Forbid jobTemplate: spec: backoffLimit: 0 template: metadata: labels: io.kompose.service: challenge spec: containers: - args: - bash - -c - echo hello from cron image: busybox:stable-glibc name: challenge restartPolicy: Never schedule: '* * * * *' ================================================ FILE: script/test/fixtures/cronjob/output-os.yaml ================================================ --- apiVersion: batch/v1 kind: CronJob metadata: labels: io.kompose.service: challenge name: challenge spec: concurrencyPolicy: Forbid jobTemplate: spec: backoffLimit: 0 template: metadata: labels: io.kompose.service: challenge spec: containers: - args: - bash - -c - echo hello from cron image: busybox:stable-glibc name: challenge restartPolicy: Never schedule: '* * * * *' ================================================ FILE: script/test/fixtures/deploy/labels/compose.yaml ================================================ services: app: image: node:18-alpine ports: - 3000:3000 deploy: labels: kompose.ephemeral-storage.request: 1Gi kompose.ephemeral-storage.limit: 1Gi ================================================ FILE: script/test/fixtures/deploy/labels/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: app name: app spec: ports: - name: "3000" port: 3000 targetPort: 3000 selector: io.kompose.service: app --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: app kompose.ephemeral-storage.limit: 1Gi kompose.ephemeral-storage.request: 1Gi name: app spec: replicas: 1 selector: matchLabels: io.kompose.service: app template: metadata: labels: io.kompose.service: app spec: containers: - image: node:18-alpine name: app ports: - containerPort: 3000 protocol: TCP resources: limits: ephemeral-storage: 1Gi requests: ephemeral-storage: 1Gi restartPolicy: Always ================================================ FILE: script/test/fixtures/deploy/placement/compose-placement.yaml ================================================ services: redis: image: redis ports: - "6379" deploy: placement: constraints: - node.hostname == machine - engine.labels.operatingsystem == ubuntu 14.04 - node.labels.foo != bar - baz != qux preferences: - spread: node.labels.zone - spread: foo - spread: node.labels.ssd ================================================ FILE: script/test/fixtures/deploy/placement/output-placement-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - machine - key: kubernetes.io/os operator: In values: - ubuntu 14.04 - key: foo operator: NotIn values: - bar containers: - image: redis name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Always topologySpreadConstraints: - labelSelector: matchLabels: io.kompose.service: redis maxSkew: 2 topologyKey: zone whenUnsatisfiable: ScheduleAnyway - labelSelector: matchLabels: io.kompose.service: redis maxSkew: 1 topologyKey: ssd whenUnsatisfiable: ScheduleAnyway ================================================ FILE: script/test/fixtures/deploy/placement/output-placement-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - machine - key: kubernetes.io/os operator: In values: - ubuntu 14.04 - key: foo operator: NotIn values: - bar containers: - image: ' ' name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Always topologySpreadConstraints: - labelSelector: matchLabels: io.kompose.service: redis maxSkew: 2 topologyKey: zone whenUnsatisfiable: ScheduleAnyway - labelSelector: matchLabels: io.kompose.service: redis maxSkew: 1 topologyKey: ssd whenUnsatisfiable: ScheduleAnyway test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/dockerfilepath/compose.yaml ================================================ services: foo: build: context: "../../script/test_in_openshift/buildconfig" dockerfile: "/Dockerfile" command: "sleep 120" ================================================ FILE: script/test/fixtures/domain/compose-v3.yaml ================================================ services: dns: image: phensley/docker-dns hostname: affy domainname: affy.com ================================================ FILE: script/test/fixtures/domain/compose.yaml ================================================ services: dns: image: phensley/docker-dns hostname: affy domainname: affy.com ================================================ FILE: script/test/fixtures/domain/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "dns" }, "name": "dns" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "dns" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "dns" } }, "spec": { "containers": [ { "image": "phensley/docker-dns", "imagePullPolicy": "", "name": "dns", "resources": {} } ], "hostname": "affy", "restartPolicy": "Always", "serviceAccountName": "", "subdomain": "affy.com", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/domain/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "dns", "creationTimestamp": null, "labels": { "io.kompose.service": "dns" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "dns" ], "from": { "kind": "ImageStreamTag", "name": "dns:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "dns" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "dns" } }, "spec": { "containers": [ { "name": "dns", "image": " ", "resources": {} } ], "restartPolicy": "Always", "hostname": "affy", "subdomain": "affy.com" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "dns", "creationTimestamp": null, "labels": { "io.kompose.service": "dns" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "phensley/docker-dns" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/entrypoint-command/compose.yaml ================================================ services: base: image: busybox entrypoint: echo command: foo labels: kompose.service.type: headless ================================================ FILE: script/test/fixtures/entrypoint-command/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "base", "creationTimestamp": null, "labels": { "io.kompose.service": "base" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "base" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "base" }, "name": "base" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "base" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "base" } }, "spec": { "containers": [ { "args": [ "foo" ], "command": [ "echo" ], "image": "busybox", "imagePullPolicy": "", "name": "base", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/entrypoint-command/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "base", "creationTimestamp": null, "labels": { "io.kompose.service": "base" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "base" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "base", "creationTimestamp": null, "labels": { "io.kompose.service": "base" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "base" ], "from": { "kind": "ImageStreamTag", "name": "base:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "base" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "base" } }, "spec": { "containers": [ { "name": "base", "image": " ", "command": [ "echo" ], "args": [ "foo" ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "base", "creationTimestamp": null, "labels": { "io.kompose.service": "base" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "busybox" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/env/compose.yaml ================================================ services: namenode: image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 environment: - CLUSTER_NAME=test env_file: - hadoop-hive-namenode.env ports: - "50070:50070" - "8020:8020" another-namenode: image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 ports: - "50070:50070" - "8020:8020" env_file: - hadoop-hive-namenode.env ================================================ FILE: script/test/fixtures/env/hadoop-hive-namenode.env ================================================ FOO=BAR BAR=FOO ================================================ FILE: script/test/fixtures/env/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: another-namenode name: another-namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: another-namenode --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: namenode name: namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: namenode --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: another-namenode name: another-namenode spec: replicas: 1 selector: matchLabels: io.kompose.service: another-namenode template: metadata: labels: io.kompose.service: another-namenode spec: containers: - envFrom: - configMapRef: name: hadoop-hive-namenode-env image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: another-namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP restartPolicy: Always --- apiVersion: v1 data: BAR: FOO FOO: BAR kind: ConfigMap metadata: labels: io.kompose.service: another-namenode-hadoop-hive-namenode-env name: hadoop-hive-namenode-env --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: namenode name: namenode spec: replicas: 1 selector: matchLabels: io.kompose.service: namenode template: metadata: labels: io.kompose.service: namenode spec: containers: - env: - name: CLUSTER_NAME value: test envFrom: - configMapRef: name: hadoop-hive-namenode-env image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/env/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: another-namenode name: another-namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: another-namenode --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: namenode name: namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: namenode --- apiVersion: v1 data: BAR: FOO FOO: BAR kind: ConfigMap metadata: labels: io.kompose.service: another-namenode-hadoop-hive-namenode-env name: hadoop-hive-namenode-env --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: another-namenode name: another-namenode spec: replicas: 1 selector: io.kompose.service: another-namenode template: metadata: labels: io.kompose.service: another-namenode spec: containers: - envFrom: - configMapRef: name: hadoop-hive-namenode-env image: ' ' name: another-namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - another-namenode from: kind: ImageStreamTag name: another-namenode:2.0.0-hadoop2.7.4-java8 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: another-namenode name: another-namenode spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: 2.0.0-hadoop2.7.4-java8 referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: namenode name: namenode spec: replicas: 1 selector: io.kompose.service: namenode template: metadata: labels: io.kompose.service: namenode spec: containers: - env: - name: CLUSTER_NAME value: test envFrom: - configMapRef: name: hadoop-hive-namenode-env image: ' ' name: namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - namenode from: kind: ImageStreamTag name: namenode:2.0.0-hadoop2.7.4-java8 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: namenode name: namenode spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: 2.0.0-hadoop2.7.4-java8 referencePolicy: type: "" ================================================ FILE: script/test/fixtures/env-dotenv/compose.yaml ================================================ services: minio: image: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z env_file: .env environment: - MINIO_ROOT_USER=${MINIO_USER} - MINIO_ROOT_PASSWORD=${MINIO_PASSWORD} - TZ=${TIMEZONE} ports: - ${MINIO_PORT}:9000 - ${MINIO_CONSOLE_PORT}:9001 ================================================ FILE: script/test/fixtures/env-dotenv/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: minio name: minio spec: ports: - name: "9000" port: 9000 targetPort: 9000 - name: "9001" port: 9001 targetPort: 9001 selector: io.kompose.service: minio --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: minio name: minio spec: replicas: 1 selector: matchLabels: io.kompose.service: minio template: metadata: labels: io.kompose.service: minio spec: containers: - env: - name: MINIO_ROOT_PASSWORD value: infini_rag_flow - name: MINIO_ROOT_USER value: rag_flow - name: TZ value: Asia/Shanghai envFrom: - configMapRef: name: env image: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z name: minio ports: - containerPort: 9000 protocol: TCP - containerPort: 9001 protocol: TCP restartPolicy: Always --- apiVersion: v1 data: MINIO_CONSOLE_PORT: "9001" MINIO_PASSWORD: infini_rag_flow MINIO_PORT: "9000" MINIO_USER: rag_flow TIMEZONE: Asia/Shanghai kind: ConfigMap metadata: labels: io.kompose.service: minio-env name: env ================================================ FILE: script/test/fixtures/env-dotenv/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: minio name: minio spec: ports: - name: "9000" port: 9000 targetPort: 9000 - name: "9001" port: 9001 targetPort: 9001 selector: io.kompose.service: minio --- apiVersion: v1 data: MINIO_CONSOLE_PORT: "9001" MINIO_PASSWORD: infini_rag_flow MINIO_PORT: "9000" MINIO_USER: rag_flow TIMEZONE: Asia/Shanghai kind: ConfigMap metadata: labels: io.kompose.service: minio-env name: env --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: minio name: minio spec: replicas: 1 selector: io.kompose.service: minio template: metadata: labels: io.kompose.service: minio spec: containers: - env: - name: MINIO_ROOT_PASSWORD value: infini_rag_flow - name: MINIO_ROOT_USER value: rag_flow - name: TZ value: Asia/Shanghai envFrom: - configMapRef: name: env image: ' ' name: minio ports: - containerPort: 9000 protocol: TCP - containerPort: 9001 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - minio from: kind: ImageStreamTag name: minio:RELEASE.2023-12-20T01-00-02Z type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: minio name: minio spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z name: RELEASE.2023-12-20T01-00-02Z referencePolicy: type: "" ================================================ FILE: script/test/fixtures/env-multiple/compose.yaml ================================================ services: namenode: image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 environment: - CLUSTER_NAME=test env_file: - env1/hadoop-hive-namenode.env ports: - "50070:50070" - "8020:8020" another-namenode: image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 ports: - "50070:50070" - "8020:8020" env_file: - env2/hadoop-hive-namenode.env ================================================ FILE: script/test/fixtures/env-multiple/env1/hadoop-hive-namenode.env ================================================ FOO=BAR ================================================ FILE: script/test/fixtures/env-multiple/env2/hadoop-hive-namenode.env ================================================ BAR=FOO ================================================ FILE: script/test/fixtures/env-multiple/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: io.kompose.service: another-namenode name: another-namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: another-namenode --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: io.kompose.service: namenode name: namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: namenode --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: io.kompose.service: another-namenode name: another-namenode spec: replicas: 1 selector: matchLabels: io.kompose.service: another-namenode strategy: {} template: metadata: creationTimestamp: null labels: io.kompose.service: another-namenode spec: containers: - env: - name: BAR valueFrom: configMapKeyRef: key: BAR name: hadoop-hive-namenode-env image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: another-namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP resources: {} restartPolicy: Always --- apiVersion: v1 data: FOO: BAR kind: ConfigMap metadata: creationTimestamp: null labels: io.kompose.service: hadoop-hive-namenode-env name: hadoop-hive-namenode-env --- apiVersion: v1 data: BAR: FOO kind: ConfigMap metadata: creationTimestamp: null labels: io.kompose.service: another-namenode-hadoop-hive-namenode-env name: hadoop-hive-namenode-env1 --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: io.kompose.service: namenode name: namenode spec: replicas: 1 selector: matchLabels: io.kompose.service: namenode strategy: {} template: metadata: creationTimestamp: null labels: io.kompose.service: namenode spec: containers: - env: - name: CLUSTER_NAME value: test - name: FOO valueFrom: configMapKeyRef: key: FOO name: hadoop-hive-namenode-env image: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 name: namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP resources: {} restartPolicy: Always ================================================ FILE: script/test/fixtures/env-multiple/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: io.kompose.service: another-namenode name: another-namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: another-namenode --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: io.kompose.service: namenode name: namenode spec: ports: - name: "50070" port: 50070 targetPort: 50070 - name: "8020" port: 8020 targetPort: 8020 selector: io.kompose.service: namenode --- apiVersion: v1 data: FOO: BAR kind: ConfigMap metadata: creationTimestamp: null labels: io.kompose.service: hadoop-hive-namenode-env name: hadoop-hive-namenode-env --- apiVersion: v1 data: BAR: FOO kind: ConfigMap metadata: creationTimestamp: null labels: io.kompose.service: another-namenode-hadoop-hive-namenode-env name: hadoop-hive-namenode-env1 --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: creationTimestamp: null labels: io.kompose.service: another-namenode name: another-namenode spec: replicas: 1 selector: io.kompose.service: another-namenode strategy: resources: {} template: metadata: creationTimestamp: null labels: io.kompose.service: another-namenode spec: containers: - env: - name: BAR valueFrom: configMapKeyRef: key: BAR name: hadoop-hive-namenode-env1 image: ' ' name: another-namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP resources: {} restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - another-namenode from: kind: ImageStreamTag name: another-namenode:2.0.0-hadoop2.7.4-java8 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: creationTimestamp: null labels: io.kompose.service: another-namenode name: another-namenode spec: lookupPolicy: local: false tags: - annotations: null from: kind: DockerImage name: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 generation: null importPolicy: {} name: 2.0.0-hadoop2.7.4-java8 referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: creationTimestamp: null labels: io.kompose.service: namenode name: namenode spec: replicas: 1 selector: io.kompose.service: namenode strategy: resources: {} template: metadata: creationTimestamp: null labels: io.kompose.service: namenode spec: containers: - env: - name: CLUSTER_NAME value: test - name: FOO valueFrom: configMapKeyRef: key: FOO name: hadoop-hive-namenode-env image: ' ' name: namenode ports: - containerPort: 50070 protocol: TCP - containerPort: 8020 protocol: TCP resources: {} restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - namenode from: kind: ImageStreamTag name: namenode:2.0.0-hadoop2.7.4-java8 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: creationTimestamp: null labels: io.kompose.service: namenode name: namenode spec: lookupPolicy: local: false tags: - annotations: null from: kind: DockerImage name: bde2020/hadoop-namenode:2.0.0-hadoop2.7.4-java8 generation: null importPolicy: {} name: 2.0.0-hadoop2.7.4-java8 referencePolicy: type: "" ================================================ FILE: script/test/fixtures/envfile-interpolation/compose.yaml ================================================ services: minio: image: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z container_name: ragflow-minio command: server --console-address ":9001" /data ports: - ${MINIO_PORT}:9000 - ${MINIO_CONSOLE_PORT}:9001 env_file: .env environment: - DOC_ENGINE=test-env restart: on-failure ================================================ FILE: script/test/fixtures/envfile-interpolation/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: minio name: minio spec: ports: - name: "9000" port: 9000 targetPort: 9000 - name: "9001" port: 9001 targetPort: 9001 selector: io.kompose.service: minio --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: minio name: minio spec: containers: - args: - server - --console-address - :9001 - /data envFrom: - configMapRef: name: env image: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z name: ragflow-minio ports: - containerPort: 9000 protocol: TCP - containerPort: 9001 protocol: TCP restartPolicy: OnFailure --- apiVersion: v1 data: COMPOSE_PROFILES: test-env DOC_ENGINE: test-env MINIO_CONSOLE_PORT: "9001" MINIO_PORT: "9000" kind: ConfigMap metadata: labels: io.kompose.service: minio-env name: env ================================================ FILE: script/test/fixtures/envfile-interpolation/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: minio name: minio spec: ports: - name: "9000" port: 9000 targetPort: 9000 - name: "9001" port: 9001 targetPort: 9001 selector: io.kompose.service: minio --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: minio name: minio spec: containers: - args: - server - --console-address - :9001 - /data envFrom: - configMapRef: name: env image: quay.io/minio/minio:RELEASE.2023-12-20T01-00-02Z name: ragflow-minio ports: - containerPort: 9000 protocol: TCP - containerPort: 9001 protocol: TCP restartPolicy: OnFailure --- apiVersion: v1 data: COMPOSE_PROFILES: test-env DOC_ENGINE: test-env MINIO_CONSOLE_PORT: "9001" MINIO_PORT: "9000" kind: ConfigMap metadata: labels: io.kompose.service: minio-env name: env ================================================ FILE: script/test/fixtures/envvars-interpolation/compose.yaml ================================================ services: myservice: image: alpine environment: PROTOCOL: 'https' DOMAIN: 'google.com' command: [ 'curl', '$PROTOCOL://$DOMAIN/', ] ================================================ FILE: script/test/fixtures/envvars-interpolation/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: myservice name: myservice spec: replicas: 1 selector: matchLabels: io.kompose.service: myservice template: metadata: labels: io.kompose.service: myservice spec: containers: - args: - curl - :/// env: - name: DOMAIN value: google.com - name: PROTOCOL value: https image: alpine name: myservice restartPolicy: Always ================================================ FILE: script/test/fixtures/envvars-interpolation/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: myservice name: myservice spec: replicas: 1 selector: io.kompose.service: myservice template: metadata: labels: io.kompose.service: myservice spec: containers: - args: - curl - :/// env: - name: DOMAIN value: google.com - name: PROTOCOL value: https image: ' ' name: myservice restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - myservice from: kind: ImageStreamTag name: myservice:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: myservice name: myservice spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: alpine name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/envvars-separators/compose.yaml ================================================ mongodb: image: mongo:latest container_name: mongodb command: mongod --smallfiles ports: - "27017:27017" volumes: - ./mongo:/data/db:rw volume_driver: local hygieia-api: image: hygieia-api:latest container_name: hygieia-api ports: - "8080:8080" volumes: - ./logs:/hygieia/logs links: - mongodb:mongo hygieia-ui: image: hygieia-ui:latest container_name: hygieia-ui ports: - "8088:80" links: - hygieia-api hygieia-github-scm-collector: image: hygieia-github-scm-collector:latest container_name: hygieia-github volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # place any values you want to change in docker-compose.override.yml # environment: # - GITHUB_HOST=github.com # - GITHUB_CRON='0 0/5 * * * *' # - GITHUB_COMMIT_THREASHOLD_DAYS=15 hygieia-jira-feature-collector: image: hygieia-jira-feature-collector:latest container_name: hygieia-jira volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api environment: # you can override these by creating a docker-compose.overider.yml and put in entries like this: #REQUIRED Entries - JIRA_BASE_URL=https://jira.atlassian.com #64-bit encoded credentials with the pattern username:password #on a mac you con create them with : echo "username:password" | base64 #reference: https://www.base64decode.org/ - JIRA_CREDENTIALS=username:password #OPTIONAL - you may want ot tweek these # - JIRA_CRON="0 * * * * *" #Start dates from which to begin collector data, if no other data is present - usually, a month back is appropriate (required) # - JIRA_DELTA_START_DATE=2015-03-01T00:00:00.000000 # - JIRA_MASTER_START_DATE=2008-01-01T00:00:00.000000 #OPTIONAL Overrides if you need them #Page size for data calls (Jira maxes at 1000) # - JIRA_PAGE_SIZE=1000 #Jira Connection Details # - JIRA_PROXY_URL= # - JIRA_PROXY_PORT= # Trending Query: Number of days in a sprint (not-required) # - JIRA_SPRINT_DAYS=60 # Trending Query: Length of sprint week (not-required) # - JIRA_SPRINT_END_PRIOR=7 #Scheduled Job prior minutes to recover data created during execution time (usually, 2 minutes is enough) # - JIRA_SCHEDULED_PRIOR_MIN=2 #Delta change date that modulates the collector item task - should be about as far back as possible, in ISO format (required) # - JIRA_DELTA_COLLECTOR_ITEM_START_DATE=2008-01-01T00:00:00.000000 #Jira Connection Details # - JIRA_QUERY_ENDPOINT=rest/api/2/ #OAuth2.0 token credentials (currently not supported in this version) # - JIRA_OAUTH_AUTH_TOKEN=sdfghjkl== # - JIRA_OAUTH_REFRESH_TOKEN=sdfagheh== # - JIRA_OAUTH_REDIRECT_URL=uri.this.is.test:uri # - JIRA_OAUTH_EXPIRE_TIME=234567890987 # In Jira, general IssueType IDs are associated to various "issue" # attributes. However, there is one attribute which this collector's # queries rely on that change between different instantiations of Jira. # Please provide a numerical ID reference to your instance's IssueType for # the lowest level of Issues (e.g., "user story") specific to your Jira # instance. Note: You can retrieve your instance's IssueType ID # listings via the following URI: https://[your-jira-domain-name]/rest/api/2/issuetype/ # - JIRA_ISSUE_TYPE_ID=Story # In Jira, your instance will have its own custom field created for "sprint" or "timebox" details, # which includes a list of information. This field allows you to specify that data field for your # instance of Jira. Note: You can retrieve your instance's sprint data field name # via the following URI, and look for a package name com.atlassian.greenhopper.service.sprint.Sprint; # your custom field name describes the values in this field: # https://[your-jira-domain-name]/rest/api/2/issue/[some-issue-name] # - JIRA_SPRINT_DATA_FIELD_NAME=customfield_10007 # In Jira, your instance will have its own custom field created for "super story" or "epic" back-end ID, # which includes a list of information. This field allows you to specify that data field for your instance # of Jira. Note: You can retrieve your instance's epic ID field name via the following URI where your # queried user story issue has a super issue (e.g., epic) tied to it; your custom field name describes the # epic value you expect to see, and is the only field that does this for a given issue: # https://[your-jira-domain-name]/rest/api/2/issue/[some-issue-name] # - JIRA_EPIC_FIELD_NAME=customfield_10400 # In Jira, your instance will have its own custom field created for "story points" # This field allows you to specify that data field for your instance # of Jira. Note: You can retrieve your instance's storypoints ID field name via the following URI where your # queried user story issue has story points set on it; your custom field name describes the # story points value you expect to see: # https://[your-jira-domain-name]/rest/api/2/issue/[some-issue-name] # - JIRA_STORY_POINTS_FIELD_NAME=customfield_10002 hygieia-jenkins-build-collector: image: hygieia-jenkins-build-collector:latest container_name: hygieia-jenkins-build volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: #Jenkins server (required) - Can provide multiple # - JENKINS_MASTER=ttp://jenkins.company.com #If using username/token for api authentication (required for Cloudbees Jenkins Ops Center) see sample # - JENKINS_OP_CENTER=http://username:token@jenkins.company.com #Another option: If using same username/password Jenkins auth - set username/apiKey to use HTTP Basic Auth (blank=no auth) # - JENKINS_USERNAME # - JENKINS_API_KEY # - JENKINS_CRON=0 0/5 * * * * #Determines if build console log is collected - defaults to false # - JENKINS_SAVE_LOG=true hygieia-jenkins-cucumber-test-collector: image: hygieia-jenkins-cucumber-test-collector:latest container_name: hygieia-jenkins-cucumber volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: #Jenkins server (required) - Can provide multiple # - JENKINS_MASTER=ttp://jenkins.company.com #If using username/token for api authentication (required for Cloudbees Jenkins Ops Center) see sample # - JENKINS_OP_CENTER=http://username:token@jenkins.company.com #Another option: If using same username/password Jenkins auth - set username/apiKey to use HTTP Basic Auth (blank=no auth) # - JENKINS_USERNAME # - JENKINS_API_KEY # - JENKINS_CRON=0 0/5 * * * * #Determines if build console log is collected - defaults to false # - JENKINS_SAVE_LOG=true hygieia-sonar-codequality-collector: image: hygieia-sonar-codequality-collector:latest container_name: hygieia-sonar-codequality volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: # - SONAR_CRON=0 0/5 * * * * # - SONAR_URL=http://localhost:9000 hygieia-chat-ops-collector: image: hygieia-chat-ops-collector:latest container_name: hygieia-chat-ops volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: # - CHATOPS_CRON=0 0/5 * * * * hygieia-subversion-scm-collector: image: hygieia-subversion-scm-collector:latest container_name: hygieia-subversion volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: # - SUBVERSION_CRON:-0 0/5 * * * * #Shared subversion username and password # - SUBVERSION_USERNAME= # - SUBVERSION_PASSWORD= #Maximum number of days to go back in time when fetching commits # - SUBVERSION_COMMIT_THRESHOLD_DAYS=15 hygieia-bitbucket-scm-collector: image: hygieia-bitbucket-scm-collector:latest container_name: hygieia-bitbucket volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: #mandatory # - BITBUCKET_HOST=mybitbucketrepo.com/ # - BITBUCKET_API=/rest/api/1.0/ # - BITBUCKET_CRON=0 0/5 * * * * # - BITBUCKET_PRODUCT=cloud #Maximum number of days to go back in time when fetching commits. Only applicable to Bitbucket Cloud. # - BITBUCKET_COMMIT_THRESHOLD_DAYS=15 #Page size for rest calls. Only applicable to Bitbucket Server. # - BITBUCKET_PAGE_SIZE=25 hygieia-versionone-collector: image: hygieia-versionone-collector:latest container_name: hygieia-versionone volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api # environment: #Page size for data calls (VersionOne recommended 2000) # - VERSIONONE_PAGE_SIZE=2000 #In-built folder housing prepared REST queries (required) # - VERSIONONE_QUERY_FOLDER=v1api-queries #Jira API Query file names (String template requires the files to have .st extension) (required) # - VERSIONONE_STORY_QUERY=story # - VERSIONONE_EPIC_QUERY=epicinfo # - VERSIONONE_PROJECT_QUERY=projectinfo # - VERSIONONE_MEMBBER_QUERY=memberinfo # - VERSIONONE_SPRINT_QUERY=sprintinfo # - VERSIONONE_TEAM_QUERY=teaminfo # - VERSIONONE_TRENDING_QUERY=trendinginfo # Trending Query: Number of days in a sprint (not-required) # - VERSIONONE_SPRINT_DAYS=60 # Trending Query: Length of sprint week (not-required) # - VERSIONONE_SPRINT_END_PRIOR=7 #Scheduled Job prior minutes to recover data created during execution time (usually, 2 minutes is enough) # - VERSIONONE_SCHEDULED_PRIOR_MIN=2 #Delta change date that modulates the collector item task - should be about as far back as possible, in ISO format (required) # - VERSIONONE_DELTA_COLLECTORITEM_START_DATE=2008-01-01T00:00:00.000000 #VersionOne Connection Details #Proxy assumes a host:port syntax # - VERSIONONE_PROXY_URL="" # - VERSIONONE_URL=https://www.versionone.com/our-company-instance/ #Access token provided by VersionOne # - VERSIONONE_ACCESS_TOKEN=accessToken #Start dates from which to begin collector data, if no other data is present - usually, a month back is appropriate (required) # - VERSIONONE_DELTA_START_DATE=2015-03-01T00:00:00.000000 # - VERSIONONE_MASTER_START_DATE=2008-01-01T00:00:00.000000 hygieia-udeploy-collector: image: hygieia-udeploy-collector:latest container_name: hygieia-udeploy volumes: - ./logs:/hygieia/logs links: - mongodb:mongo - hygieia-api environment: #UDeploy server (required) - Can provide multiple - UDEPLOY_URL:-http://udeploy.company.com #UDeploy user name (required) - UDEPLOY_USERNAME:-bobama #UDeploy password (required) - UDEPLOY_PASSWORD:-s3cr3t #Collector schedule (required) # - UDEPLOY_CRON:-0 0/5 * * * * ================================================ FILE: script/test/fixtures/envvars-separators/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "hygieia-api", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-api" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "hygieia-api" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "hygieia-ui", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-ui" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8088", "port": 8088, "targetPort": 80 } ], "selector": { "io.kompose.service": "hygieia-ui" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mongodb", "creationTimestamp": null, "labels": { "io.kompose.service": "mongodb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "27017", "port": 27017, "targetPort": 27017 } ], "selector": { "io.kompose.service": "mongodb" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-api" }, "name": "hygieia-api" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-api" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-api" } }, "spec": { "containers": [ { "image": "hygieia-api:latest", "imagePullPolicy": "", "name": "hygieia-api", "ports": [ { "containerPort": 8080 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-api-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-api-claim0", "persistentVolumeClaim": { "claimName": "hygieia-api-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-api-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-api-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-bitbucket-scm-collector" }, "name": "hygieia-bitbucket-scm-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-bitbucket-scm-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-bitbucket-scm-collector" } }, "spec": { "containers": [ { "image": "hygieia-bitbucket-scm-collector:latest", "imagePullPolicy": "", "name": "hygieia-bitbucket", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-bitbucket-scm-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-bitbucket-scm-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-bitbucket-scm-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-bitbucket-scm-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-bitbucket-scm-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-chat-ops-collector" }, "name": "hygieia-chat-ops-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-chat-ops-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-chat-ops-collector" } }, "spec": { "containers": [ { "image": "hygieia-chat-ops-collector:latest", "imagePullPolicy": "", "name": "hygieia-chat-ops", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-chat-ops-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-chat-ops-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-chat-ops-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-chat-ops-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-chat-ops-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-github-scm-collector" }, "name": "hygieia-github-scm-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-github-scm-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-github-scm-collector" } }, "spec": { "containers": [ { "image": "hygieia-github-scm-collector:latest", "imagePullPolicy": "", "name": "hygieia-github", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-github-scm-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-github-scm-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-github-scm-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-github-scm-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-github-scm-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-build-collector" }, "name": "hygieia-jenkins-build-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-jenkins-build-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-build-collector" } }, "spec": { "containers": [ { "image": "hygieia-jenkins-build-collector:latest", "imagePullPolicy": "", "name": "hygieia-jenkins-build", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-jenkins-build-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-jenkins-build-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-jenkins-build-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-jenkins-build-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-build-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-cucumber-test-collector" }, "name": "hygieia-jenkins-cucumber-test-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-jenkins-cucumber-test-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-cucumber-test-collector" } }, "spec": { "containers": [ { "image": "hygieia-jenkins-cucumber-test-collector:latest", "imagePullPolicy": "", "name": "hygieia-jenkins-cucumber", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-jenkins-cucumber-test-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-jenkins-cucumber-test-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-jenkins-cucumber-test-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-jenkins-cucumber-test-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jenkins-cucumber-test-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jira-feature-collector" }, "name": "hygieia-jira-feature-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-jira-feature-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jira-feature-collector" } }, "spec": { "containers": [ { "env": [ { "name": "JIRA_BASE_URL", "value": "https://jira.atlassian.com" }, { "name": "JIRA_CREDENTIALS", "value": "username:password" } ], "image": "hygieia-jira-feature-collector:latest", "imagePullPolicy": "", "name": "hygieia-jira", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-jira-feature-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-jira-feature-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-jira-feature-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-jira-feature-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-jira-feature-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-sonar-codequality-collector" }, "name": "hygieia-sonar-codequality-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-sonar-codequality-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-sonar-codequality-collector" } }, "spec": { "containers": [ { "image": "hygieia-sonar-codequality-collector:latest", "imagePullPolicy": "", "name": "hygieia-sonar-codequality", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-sonar-codequality-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-sonar-codequality-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-sonar-codequality-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-sonar-codequality-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-sonar-codequality-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-subversion-scm-collector" }, "name": "hygieia-subversion-scm-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-subversion-scm-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-subversion-scm-collector" } }, "spec": { "containers": [ { "image": "hygieia-subversion-scm-collector:latest", "imagePullPolicy": "", "name": "hygieia-subversion", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-subversion-scm-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-subversion-scm-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-subversion-scm-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-subversion-scm-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-subversion-scm-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-udeploy-collector" }, "name": "hygieia-udeploy-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-udeploy-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-udeploy-collector" } }, "spec": { "containers": [ { "env": [ { "name": "UDEPLOY_PASSWORD", "value": "-s3cr3t" }, { "name": "UDEPLOY_URL", "value": "-http://udeploy.company.com" }, { "name": "UDEPLOY_USERNAME", "value": "-bobama" } ], "image": "hygieia-udeploy-collector:latest", "imagePullPolicy": "", "name": "hygieia-udeploy", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-udeploy-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-udeploy-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-udeploy-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-udeploy-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-udeploy-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-ui" }, "name": "hygieia-ui" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-ui" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-ui" } }, "spec": { "containers": [ { "image": "hygieia-ui:latest", "imagePullPolicy": "", "name": "hygieia-ui", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-versionone-collector" }, "name": "hygieia-versionone-collector" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "hygieia-versionone-collector" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-versionone-collector" } }, "spec": { "containers": [ { "image": "hygieia-versionone-collector:latest", "imagePullPolicy": "", "name": "hygieia-versionone", "resources": {}, "volumeMounts": [ { "mountPath": "/hygieia/logs", "name": "hygieia-versionone-collector-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "hygieia-versionone-collector-claim0", "persistentVolumeClaim": { "claimName": "hygieia-versionone-collector-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "hygieia-versionone-collector-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "hygieia-versionone-collector-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mongodb" }, "name": "mongodb" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "mongodb" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mongodb" } }, "spec": { "containers": [ { "args": [ "mongod", "--smallfiles" ], "image": "mongo:latest", "imagePullPolicy": "", "name": "mongodb", "ports": [ { "containerPort": 27017 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/data/db", "name": "mongodb-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "mongodb-claim0", "persistentVolumeClaim": { "claimName": "mongodb-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "mongodb-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "mongodb-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/envvars-with-status/compose.yaml ================================================ services: app: image: node:18-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 environment: TOPICS: foo:1,status:2,bar:3 OTHER_ENV: example ================================================ FILE: script/test/fixtures/envvars-with-status/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: app name: app spec: ports: - name: "3000" port: 3000 targetPort: 3000 selector: io.kompose.service: app --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: app name: app spec: replicas: 1 selector: matchLabels: io.kompose.service: app template: metadata: labels: io.kompose.service: app spec: containers: - args: - sh - -c - yarn install && yarn run dev env: - name: OTHER_ENV value: example - name: TOPICS value: foo:1,status:2,bar:3 image: node:18-alpine name: app ports: - containerPort: 3000 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/envvars-with-status/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: app name: app spec: ports: - name: "3000" port: 3000 targetPort: 3000 selector: io.kompose.service: app --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: app name: app spec: replicas: 1 selector: io.kompose.service: app template: metadata: labels: io.kompose.service: app spec: containers: - args: - sh - -c - yarn install && yarn run dev env: - name: OTHER_ENV value: example - name: TOPICS value: foo:1,status:2,bar:3 image: ' ' name: app ports: - containerPort: 3000 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - app from: kind: ImageStreamTag name: app:18-alpine type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: app name: app spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: node:18-alpine name: 18-alpine referencePolicy: type: "" ================================================ FILE: script/test/fixtures/etherpad/README.md ================================================ ## Compose Etherpad Etherpad and Mariadb ### Usage The simplest thing to do: ```bash export $(cat envs) docker-compose up ``` To customize the values edit `envs` file. ================================================ FILE: script/test/fixtures/etherpad/docker-compose-no-image.yml ================================================ services: mariadb: ports: - 3306 etherpad: ports: - "80:9001" ================================================ FILE: script/test/fixtures/etherpad/docker-compose-no-ports.yml ================================================ services: mariadb: image: centos/mariadb ================================================ FILE: script/test/fixtures/etherpad/docker-compose.yml ================================================ services: mariadb: image: centos/mariadb ports: - "$DB_PORT" environment: MYSQL_ROOT_PASSWORD: $ROOT_PASS MYSQL_DATABASE: $DB_NAME MYSQL_PASSWORD: $DB_PASS MYSQL_USER: $DB_USER volumes: - /var/lib/mysql etherpad: image: centos/etherpad ports: - "80:9001" depends_on: - mariadb environment: DB_HOST: $DB_HOST DB_DBID: $DB_NAME DB_PASS: $DB_PASS DB_PORT: $DB_PORT DB_USER: $DB_USER ================================================ FILE: script/test/fixtures/etherpad/envs ================================================ DB_HOST=mariadb ROOT_PASS=etherpad DB_NAME=etherpad DB_PASS=etherpad DB_USER=etherpad DB_PORT=3306 ================================================ FILE: script/test/fixtures/etherpad/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "etherpad", "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 9001 } ], "selector": { "io.kompose.service": "etherpad" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3306", "port": 3306, "targetPort": 3306 } ], "selector": { "io.kompose.service": "mariadb" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" }, "name": "etherpad" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "etherpad" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" } }, "spec": { "containers": [ { "env": [ { "name": "DB_DBID", "value": "etherpad" }, { "name": "DB_HOST", "value": "mariadb" }, { "name": "DB_PASS", "value": "etherpad" }, { "name": "DB_PORT", "value": "3306" }, { "name": "DB_USER", "value": "etherpad" } ], "image": "centos/etherpad", "imagePullPolicy": "", "name": "etherpad", "ports": [ { "containerPort": 9001 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "name": "mariadb" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "mariadb" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "containers": [ { "env": [ { "name": "MYSQL_DATABASE", "value": "etherpad" }, { "name": "MYSQL_PASSWORD", "value": "etherpad" }, { "name": "MYSQL_ROOT_PASSWORD", "value": "etherpad" }, { "name": "MYSQL_USER", "value": "etherpad" } ], "image": "centos/mariadb", "imagePullPolicy": "", "name": "mariadb", "ports": [ { "containerPort": 3306 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/mysql", "name": "mariadb-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "mariadb-claim0", "persistentVolumeClaim": { "claimName": "mariadb-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "mariadb-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/etherpad/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "etherpad", "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 9001 } ], "selector": { "io.kompose.service": "etherpad" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3306", "port": 3306, "targetPort": 3306 } ], "selector": { "io.kompose.service": "mariadb" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "etherpad", "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "etherpad" ], "from": { "kind": "ImageStreamTag", "name": "etherpad:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "etherpad" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" } }, "spec": { "containers": [ { "name": "etherpad", "image": " ", "ports": [ { "containerPort": 9001 } ], "env": [ { "name": "DB_DBID", "value": "etherpad" }, { "name": "DB_HOST", "value": "mariadb" }, { "name": "DB_PASS", "value": "etherpad" }, { "name": "DB_PORT", "value": "3306" }, { "name": "DB_USER", "value": "etherpad" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "etherpad", "creationTimestamp": null, "labels": { "io.kompose.service": "etherpad" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "centos/etherpad" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "mariadb" ], "from": { "kind": "ImageStreamTag", "name": "mariadb:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "mariadb" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "volumes": [ { "name": "mariadb-claim0", "persistentVolumeClaim": { "claimName": "mariadb-claim0" } } ], "containers": [ { "name": "mariadb", "image": " ", "ports": [ { "containerPort": 3306 } ], "env": [ { "name": "MYSQL_DATABASE", "value": "etherpad" }, { "name": "MYSQL_PASSWORD", "value": "etherpad" }, { "name": "MYSQL_ROOT_PASSWORD", "value": "etherpad" }, { "name": "MYSQL_USER", "value": "etherpad" } ], "resources": {}, "volumeMounts": [ { "name": "mariadb-claim0", "mountPath": "/var/lib/mysql" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "centos/mariadb" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "mariadb-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-counter-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-counter-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/examples/output-counter-v3-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-counter-v3-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.service.type": "NodePort", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.service.type": "NodePort", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/examples/output-gitlab-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "10080", "port": 10080, "targetPort": 80 }, { "name": "10022", "port": 10022, "targetPort": 22 } ], "selector": { "io.kompose.service": "gitlab" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "name": "gitlab" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "gitlab" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "NodePort", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "containers": [ { "env": [ { "name": "DB_ADAPTER", "value": "postgresql" }, { "name": "DB_HOST", "value": "postgresql" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_PORT", "value": "5432" }, { "name": "DB_USER", "value": "gitlab" }, { "name": "DEBUG", "value": "false" }, { "name": "GITLAB_BACKUP_SCHEDULE", "value": "daily" }, { "name": "GITLAB_BACKUP_TIME", "value": "01:00" }, { "name": "GITLAB_EMAIL", "value": "notifications@example.com" }, { "name": "GITLAB_EMAIL_REPLY_TO", "value": "noreply@example.com" }, { "name": "GITLAB_HOST", "value": "localhost" }, { "name": "GITLAB_HTTPS", "value": "false" }, { "name": "GITLAB_INCOMING_EMAIL_ADDRESS", "value": "reply@example.com" }, { "name": "GITLAB_NOTIFY_ON_BROKEN_BUILDS", "value": "true" }, { "name": "GITLAB_NOTIFY_PUSHER", "value": "false" }, { "name": "GITLAB_PORT", "value": "10080" }, { "name": "GITLAB_RELATIVE_URL_ROOT" }, { "name": "GITLAB_ROOT_EMAIL" }, { "name": "GITLAB_ROOT_PASSWORD" }, { "name": "GITLAB_SECRETS_DB_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SECRETS_OTP_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SECRETS_SECRET_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SSH_PORT", "value": "10022" }, { "name": "GITLAB_TIMEZONE", "value": "Kolkata" }, { "name": "IMAP_ENABLED", "value": "false" }, { "name": "IMAP_HOST", "value": "imap.gmail.com" }, { "name": "IMAP_PASS", "value": "password" }, { "name": "IMAP_PORT", "value": "993" }, { "name": "IMAP_SSL", "value": "true" }, { "name": "IMAP_STARTTLS", "value": "false" }, { "name": "IMAP_USER", "value": "mailer@example.com" }, { "name": "OAUTH_ALLOW_SSO" }, { "name": "OAUTH_AUTH0_CLIENT_ID" }, { "name": "OAUTH_AUTH0_CLIENT_SECRET" }, { "name": "OAUTH_AUTH0_DOMAIN" }, { "name": "OAUTH_AUTO_LINK_LDAP_USER", "value": "false" }, { "name": "OAUTH_AUTO_LINK_SAML_USER", "value": "false" }, { "name": "OAUTH_AUTO_SIGN_IN_WITH_PROVIDER" }, { "name": "OAUTH_AZURE_API_KEY" }, { "name": "OAUTH_AZURE_API_SECRET" }, { "name": "OAUTH_AZURE_TENANT_ID" }, { "name": "OAUTH_BITBUCKET_API_KEY" }, { "name": "OAUTH_BITBUCKET_APP_SECRET" }, { "name": "OAUTH_BLOCK_AUTO_CREATED_USERS", "value": "true" }, { "name": "OAUTH_CAS3_DISABLE_SSL_VERIFICATION", "value": "false" }, { "name": "OAUTH_CAS3_LABEL", "value": "cas3" }, { "name": "OAUTH_CAS3_LOGIN_URL", "value": "/cas/login" }, { "name": "OAUTH_CAS3_LOGOUT_URL", "value": "/cas/logout" }, { "name": "OAUTH_CAS3_SERVER" }, { "name": "OAUTH_CAS3_VALIDATE_URL", "value": "/cas/p3/serviceValidate" }, { "name": "OAUTH_CROWD_APP_NAME" }, { "name": "OAUTH_CROWD_APP_PASSWORD" }, { "name": "OAUTH_CROWD_SERVER_URL" }, { "name": "OAUTH_ENABLED", "value": "false" }, { "name": "OAUTH_EXTERNAL_PROVIDERS" }, { "name": "OAUTH_FACEBOOK_API_KEY" }, { "name": "OAUTH_FACEBOOK_APP_SECRET" }, { "name": "OAUTH_GITHUB_API_KEY" }, { "name": "OAUTH_GITHUB_APP_SECRET" }, { "name": "OAUTH_GITHUB_URL" }, { "name": "OAUTH_GITHUB_VERIFY_SSL" }, { "name": "OAUTH_GITLAB_API_KEY" }, { "name": "OAUTH_GITLAB_APP_SECRET" }, { "name": "OAUTH_GOOGLE_API_KEY" }, { "name": "OAUTH_GOOGLE_APP_SECRET" }, { "name": "OAUTH_GOOGLE_RESTRICT_DOMAIN" }, { "name": "OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME" }, { "name": "OAUTH_SAML_EXTERNAL_GROUPS" }, { "name": "OAUTH_SAML_GROUPS_ATTRIBUTE" }, { "name": "OAUTH_SAML_IDP_CERT_FINGERPRINT" }, { "name": "OAUTH_SAML_IDP_SSO_TARGET_URL" }, { "name": "OAUTH_SAML_ISSUER" }, { "name": "OAUTH_SAML_LABEL", "value": "\"Our SAML Provider\"" }, { "name": "OAUTH_SAML_NAME_IDENTIFIER_FORMAT", "value": "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" }, { "name": "OAUTH_TWITTER_API_KEY" }, { "name": "OAUTH_TWITTER_APP_SECRET" }, { "name": "REDIS_HOST", "value": "redis" }, { "name": "REDIS_PORT", "value": "6379" }, { "name": "SMTP_AUTHENTICATION", "value": "login" }, { "name": "SMTP_DOMAIN", "value": "www.example.com" }, { "name": "SMTP_ENABLED", "value": "false" }, { "name": "SMTP_HOST", "value": "smtp.gmail.com" }, { "name": "SMTP_PASS", "value": "password" }, { "name": "SMTP_PORT", "value": "587" }, { "name": "SMTP_STARTTLS", "value": "true" }, { "name": "SMTP_USER", "value": "mailer@example.com" }, { "name": "SSL_SELF_SIGNED", "value": "false" }, { "name": "TZ", "value": "Asia/Kolkata" } ], "image": "sameersbn/gitlab:8.13.3", "imagePullPolicy": "", "name": "gitlab", "ports": [ { "containerPort": 80 }, { "containerPort": 22 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/home/git/data", "name": "gitlab-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "gitlab-claim0", "persistentVolumeClaim": { "claimName": "gitlab-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "gitlab-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "name": "postgresql" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "postgresql" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "containers": [ { "env": [ { "name": "DB_EXTENSION", "value": "pg_trgm" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_USER", "value": "gitlab" } ], "image": "sameersbn/postgresql:9.5-3", "imagePullPolicy": "", "name": "postgresql", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/postgresql", "name": "postgresql-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "postgresql-claim0", "persistentVolumeClaim": { "claimName": "postgresql-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "postgresql-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "args": [ "--loglevel warning" ], "image": "sameersbn/redis:latest", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/redis", "name": "redis-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "redis-claim0", "persistentVolumeClaim": { "claimName": "redis-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "redis-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-gitlab-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.service.type": "NodePort", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "10080", "port": 10080, "targetPort": 80 }, { "name": "10022", "port": 10022, "targetPort": 22 } ], "selector": { "io.kompose.service": "gitlab" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.service.type": "NodePort", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "gitlab" ], "from": { "kind": "ImageStreamTag", "name": "gitlab:8.13.3" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "gitlab" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "volumes": [ { "name": "gitlab-claim0", "persistentVolumeClaim": { "claimName": "gitlab-claim0" } } ], "containers": [ { "name": "gitlab", "image": " ", "ports": [ { "containerPort": 80 }, { "containerPort": 22 } ], "env": [ { "name": "DB_ADAPTER", "value": "postgresql" }, { "name": "DB_HOST", "value": "postgresql" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_PORT", "value": "5432" }, { "name": "DB_USER", "value": "gitlab" }, { "name": "DEBUG", "value": "false" }, { "name": "GITLAB_BACKUP_SCHEDULE", "value": "daily" }, { "name": "GITLAB_BACKUP_TIME", "value": "01:00" }, { "name": "GITLAB_EMAIL", "value": "notifications@example.com" }, { "name": "GITLAB_EMAIL_REPLY_TO", "value": "noreply@example.com" }, { "name": "GITLAB_HOST", "value": "localhost" }, { "name": "GITLAB_HTTPS", "value": "false" }, { "name": "GITLAB_INCOMING_EMAIL_ADDRESS", "value": "reply@example.com" }, { "name": "GITLAB_NOTIFY_ON_BROKEN_BUILDS", "value": "true" }, { "name": "GITLAB_NOTIFY_PUSHER", "value": "false" }, { "name": "GITLAB_PORT", "value": "10080" }, { "name": "GITLAB_RELATIVE_URL_ROOT" }, { "name": "GITLAB_ROOT_EMAIL" }, { "name": "GITLAB_ROOT_PASSWORD" }, { "name": "GITLAB_SECRETS_DB_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SECRETS_OTP_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SECRETS_SECRET_KEY_BASE", "value": "long-and-random-alphanumeric-string" }, { "name": "GITLAB_SSH_PORT", "value": "10022" }, { "name": "GITLAB_TIMEZONE", "value": "Kolkata" }, { "name": "IMAP_ENABLED", "value": "false" }, { "name": "IMAP_HOST", "value": "imap.gmail.com" }, { "name": "IMAP_PASS", "value": "password" }, { "name": "IMAP_PORT", "value": "993" }, { "name": "IMAP_SSL", "value": "true" }, { "name": "IMAP_STARTTLS", "value": "false" }, { "name": "IMAP_USER", "value": "mailer@example.com" }, { "name": "OAUTH_ALLOW_SSO" }, { "name": "OAUTH_AUTH0_CLIENT_ID" }, { "name": "OAUTH_AUTH0_CLIENT_SECRET" }, { "name": "OAUTH_AUTH0_DOMAIN" }, { "name": "OAUTH_AUTO_LINK_LDAP_USER", "value": "false" }, { "name": "OAUTH_AUTO_LINK_SAML_USER", "value": "false" }, { "name": "OAUTH_AUTO_SIGN_IN_WITH_PROVIDER" }, { "name": "OAUTH_AZURE_API_KEY" }, { "name": "OAUTH_AZURE_API_SECRET" }, { "name": "OAUTH_AZURE_TENANT_ID" }, { "name": "OAUTH_BITBUCKET_API_KEY" }, { "name": "OAUTH_BITBUCKET_APP_SECRET" }, { "name": "OAUTH_BLOCK_AUTO_CREATED_USERS", "value": "true" }, { "name": "OAUTH_CAS3_DISABLE_SSL_VERIFICATION", "value": "false" }, { "name": "OAUTH_CAS3_LABEL", "value": "cas3" }, { "name": "OAUTH_CAS3_LOGIN_URL", "value": "/cas/login" }, { "name": "OAUTH_CAS3_LOGOUT_URL", "value": "/cas/logout" }, { "name": "OAUTH_CAS3_SERVER" }, { "name": "OAUTH_CAS3_VALIDATE_URL", "value": "/cas/p3/serviceValidate" }, { "name": "OAUTH_CROWD_APP_NAME" }, { "name": "OAUTH_CROWD_APP_PASSWORD" }, { "name": "OAUTH_CROWD_SERVER_URL" }, { "name": "OAUTH_ENABLED", "value": "false" }, { "name": "OAUTH_EXTERNAL_PROVIDERS" }, { "name": "OAUTH_FACEBOOK_API_KEY" }, { "name": "OAUTH_FACEBOOK_APP_SECRET" }, { "name": "OAUTH_GITHUB_API_KEY" }, { "name": "OAUTH_GITHUB_APP_SECRET" }, { "name": "OAUTH_GITHUB_URL" }, { "name": "OAUTH_GITHUB_VERIFY_SSL" }, { "name": "OAUTH_GITLAB_API_KEY" }, { "name": "OAUTH_GITLAB_APP_SECRET" }, { "name": "OAUTH_GOOGLE_API_KEY" }, { "name": "OAUTH_GOOGLE_APP_SECRET" }, { "name": "OAUTH_GOOGLE_RESTRICT_DOMAIN" }, { "name": "OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME" }, { "name": "OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME" }, { "name": "OAUTH_SAML_EXTERNAL_GROUPS" }, { "name": "OAUTH_SAML_GROUPS_ATTRIBUTE" }, { "name": "OAUTH_SAML_IDP_CERT_FINGERPRINT" }, { "name": "OAUTH_SAML_IDP_SSO_TARGET_URL" }, { "name": "OAUTH_SAML_ISSUER" }, { "name": "OAUTH_SAML_LABEL", "value": "\"Our SAML Provider\"" }, { "name": "OAUTH_SAML_NAME_IDENTIFIER_FORMAT", "value": "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" }, { "name": "OAUTH_TWITTER_API_KEY" }, { "name": "OAUTH_TWITTER_APP_SECRET" }, { "name": "REDIS_HOST", "value": "redis" }, { "name": "REDIS_PORT", "value": "6379" }, { "name": "SMTP_AUTHENTICATION", "value": "login" }, { "name": "SMTP_DOMAIN", "value": "www.example.com" }, { "name": "SMTP_ENABLED", "value": "false" }, { "name": "SMTP_HOST", "value": "smtp.gmail.com" }, { "name": "SMTP_PASS", "value": "password" }, { "name": "SMTP_PORT", "value": "587" }, { "name": "SMTP_STARTTLS", "value": "true" }, { "name": "SMTP_USER", "value": "mailer@example.com" }, { "name": "SSL_SELF_SIGNED", "value": "false" }, { "name": "TZ", "value": "Asia/Kolkata" } ], "resources": {}, "volumeMounts": [ { "name": "gitlab-claim0", "mountPath": "/home/git/data" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "tags": [ { "name": "8.13.3", "annotations": null, "from": { "kind": "DockerImage", "name": "sameersbn/gitlab:8.13.3" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "gitlab-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "postgresql" ], "from": { "kind": "ImageStreamTag", "name": "postgresql:9.5-3" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "postgresql" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "volumes": [ { "name": "postgresql-claim0", "persistentVolumeClaim": { "claimName": "postgresql-claim0" } } ], "containers": [ { "name": "postgresql", "image": " ", "ports": [ { "containerPort": 5432 } ], "env": [ { "name": "DB_EXTENSION", "value": "pg_trgm" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_USER", "value": "gitlab" } ], "resources": {}, "volumeMounts": [ { "name": "postgresql-claim0", "mountPath": "/var/lib/postgresql" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "tags": [ { "name": "9.5-3", "annotations": null, "from": { "kind": "DockerImage", "name": "sameersbn/postgresql:9.5-3" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "postgresql-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "redis-claim0", "persistentVolumeClaim": { "claimName": "redis-claim0" } } ], "containers": [ { "name": "redis", "image": " ", "args": [ "--loglevel warning" ], "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "name": "redis-claim0", "mountPath": "/var/lib/redis" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "sameersbn/redis:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "redis-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "containers": [ { "name": "frontend", "image": "gcr.io/google-samples/gb-frontend:v4", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "containers": [ { "name": "redis-master", "image": "registry.k8s.io/redis:e2e", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "containers": [ { "name": "redis-replica", "image": "registry.k8s.io/redis-slave:v2", "ports": [ { "containerPort": 6379 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "frontend" ], "from": { "kind": "ImageStreamTag", "name": "frontend:v4" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "frontend" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "name": "frontend", "image": " ", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "tags": [ { "name": "v4", "annotations": null, "from": { "kind": "DockerImage", "name": "gcr.io/google-samples/gb-frontend:v4" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-master" ], "from": { "kind": "ImageStreamTag", "name": "redis-master:e2e" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-master" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "name": "redis-master", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "tags": [ { "name": "e2e", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis:e2e" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-replica" ], "from": { "kind": "ImageStreamTag", "name": "redis-replica:v1" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-replica" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "name": "redis-replica", "image": " ", "ports": [ { "containerPort": 6379 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "tags": [ { "name": "v1", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis-slave:v2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/examples/output-v3-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "name": "frontend" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "frontend" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "gcr.io/google-samples/gb-frontend:v4", "imagePullPolicy": "", "name": "frontend", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-master" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "name": "redis-replica" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-replica" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "registry.k8s.io/redis-slave:v2", "imagePullPolicy": "", "name": "redis-replica", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-v3-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.service.type": "LoadBalancer", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "frontend" ], "from": { "kind": "ImageStreamTag", "name": "frontend:v4" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "frontend" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "name": "frontend", "image": " ", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "tags": [ { "name": "v4", "annotations": null, "from": { "kind": "DockerImage", "name": "gcr.io/google-samples/gb-frontend:v4" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-master" ], "from": { "kind": "ImageStreamTag", "name": "redis-master:e2e" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-master" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "name": "redis-master", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "tags": [ { "name": "e2e", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis:e2e" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-replica" ], "from": { "kind": "ImageStreamTag", "name": "redis-replica:v1" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-replica" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "name": "redis-replica", "image": " ", "ports": [ { "containerPort": 6379 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "tags": [ { "name": "v1", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis-slave:v2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/examples/output-voting-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "com.example.description": "Postgres Database", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "result", "creationTimestamp": null, "labels": { "io.kompose.service": "result" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5001", "port": 5001, "targetPort": 80 } ], "selector": { "io.kompose.service": "result" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "annotations": { "com.example.description": "Vote", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 80 } ], "selector": { "io.kompose.service": "vote" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "com.example.description": "Postgres Database", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "db" } }, "strategy": {}, "template": { "metadata": { "annotations": { "com.example.description": "Postgres Database", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "image": "postgres:9.4", "imagePullPolicy": "", "name": "db", "ports": [ { "containerPort": 5432 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:alpine", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "result" }, "name": "result" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "result" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "result" } }, "spec": { "containers": [ { "image": "tmadams333/example-voting-app-result:latest", "imagePullPolicy": "", "name": "result", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "com.example.description": "Vote", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "name": "vote" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "vote" } }, "strategy": {}, "template": { "metadata": { "annotations": { "com.example.description": "Vote", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "containers": [ { "image": "docker/example-voting-app-vote:latest", "imagePullPolicy": "", "name": "vote", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "worker" }, "name": "worker" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "worker" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "worker" } }, "spec": { "containers": [ { "image": "docker/example-voting-app-worker:latest", "imagePullPolicy": "", "name": "worker", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/examples/output-voting-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "com.example.description": "Postgres Database", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "result", "creationTimestamp": null, "labels": { "io.kompose.service": "result" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5001", "port": 5001, "targetPort": 80 } ], "selector": { "io.kompose.service": "result" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "annotations": { "com.example.description": "Vote", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 80 } ], "selector": { "io.kompose.service": "vote" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "com.example.description": "Postgres Database", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "db" ], "from": { "kind": "ImageStreamTag", "name": "db:9.4" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "db" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "name": "db", "image": " ", "ports": [ { "containerPort": 5432 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "tags": [ { "name": "9.4", "annotations": null, "from": { "kind": "DockerImage", "name": "postgres:9.4" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:alpine" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "alpine", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:alpine" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "result", "creationTimestamp": null, "labels": { "io.kompose.service": "result" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "result" ], "from": { "kind": "ImageStreamTag", "name": "result:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "result" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "result" } }, "spec": { "containers": [ { "name": "result", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "result", "creationTimestamp": null, "labels": { "io.kompose.service": "result" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tmadams333/example-voting-app-result:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "annotations": { "com.example.description": "Vote", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "vote" ], "from": { "kind": "ImageStreamTag", "name": "vote:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "vote" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "containers": [ { "name": "vote", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "docker/example-voting-app-vote:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "worker", "creationTimestamp": null, "labels": { "io.kompose.service": "worker" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "worker" ], "from": { "kind": "ImageStreamTag", "name": "worker:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "worker" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "worker" } }, "spec": { "containers": [ { "name": "worker", "image": " ", "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "worker", "creationTimestamp": null, "labels": { "io.kompose.service": "worker" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "docker/example-voting-app-worker:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/expose/compose.yaml ================================================ services: web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: "batman.example.com/dev,batwoman.example.com" kompose.service.expose.tls-secret: "test-secret" kompose.service.expose.ingress-class-name: "nginx" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/expose/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis:3.0 name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: tuna/docker-counter23 name: web ports: - containerPort: 5000 protocol: TCP restartPolicy: Always --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: labels: io.kompose.service: web name: web spec: ingressClassName: nginx rules: - host: batman.example.com http: paths: - backend: service: name: web port: number: 5000 path: /dev pathType: Prefix - host: batwoman.example.com http: paths: - backend: service: name: web port: number: 5000 path: / pathType: Prefix tls: - hosts: - batman.example.com - batwoman.example.com secretName: test-secret ================================================ FILE: script/test/fixtures/expose/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web spec: ports: - name: "5000" port: 5000 targetPort: 5000 selector: io.kompose.service: web --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' name: redis ports: - containerPort: 6379 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:3.0 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis:3.0 name: "3.0" referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: ' ' name: web ports: - containerPort: 5000 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: tuna/docker-counter23 name: latest referencePolicy: type: "" --- apiVersion: v1 kind: Route metadata: labels: io.kompose.service: web name: web spec: host: batman.example.com/dev,batwoman.example.com port: targetPort: 5000 to: kind: Service name: web ================================================ FILE: script/test/fixtures/external-traffic-policy/compose-v1.yaml ================================================ services: front-end: image: gcr.io/google-samples/gb-frontend:v4 environment: - GET_HOSTS_FROM=dns ports: - 80:80 labels: kompose.service.expose: lb kompose.service.expose.ingress-class-name: nginx kompose.service.external-traffic-policy: local kompose.service.type: loadbalancer ================================================ FILE: script/test/fixtures/external-traffic-policy/compose-v2.yaml ================================================ services: front-end: image: gcr.io/google-samples/gb-frontend:v4 environment: - GET_HOSTS_FROM=dns ports: - 80:80 labels: kompose.service.expose: lb kompose.service.expose.ingress-class-name: nginx kompose.service.external-traffic-policy: local kompose.service.type: headless ================================================ FILE: script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: front-end-tcp name: front-end-tcp spec: externalTrafficPolicy: Local ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: front-end type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: front-end name: front-end spec: replicas: 1 selector: matchLabels: io.kompose.service: front-end template: metadata: labels: io.kompose.service: front-end spec: containers: - env: - name: GET_HOSTS_FROM value: dns image: gcr.io/google-samples/gb-frontend:v4 name: front-end ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: front-end name: front-end spec: clusterIP: None ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: front-end type: ClusterIP --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: front-end name: front-end spec: replicas: 1 selector: matchLabels: io.kompose.service: front-end template: metadata: labels: io.kompose.service: front-end spec: containers: - env: - name: GET_HOSTS_FROM value: dns image: gcr.io/google-samples/gb-frontend:v4 name: front-end ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: labels: io.kompose.service: front-end name: front-end spec: ingressClassName: nginx rules: - host: lb http: paths: - backend: service: name: front-end port: number: 80 path: / pathType: Prefix ================================================ FILE: script/test/fixtures/external-traffic-policy/output-os-v1.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: front-end-tcp name: front-end-tcp spec: externalTrafficPolicy: Local ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: front-end type: LoadBalancer --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: front-end name: front-end spec: replicas: 1 selector: io.kompose.service: front-end template: metadata: labels: io.kompose.service: front-end spec: containers: - env: - name: GET_HOSTS_FROM value: dns image: ' ' name: front-end ports: - containerPort: 80 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - front-end from: kind: ImageStreamTag name: front-end:v4 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: front-end name: front-end spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: gcr.io/google-samples/gb-frontend:v4 name: v4 referencePolicy: type: "" ================================================ FILE: script/test/fixtures/external-traffic-policy/output-os-v2.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: front-end name: front-end spec: clusterIP: None ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: front-end type: ClusterIP --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: front-end name: front-end spec: replicas: 1 selector: io.kompose.service: front-end template: metadata: labels: io.kompose.service: front-end spec: containers: - env: - name: GET_HOSTS_FROM value: dns image: ' ' name: front-end ports: - containerPort: 80 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - front-end from: kind: ImageStreamTag name: front-end:v4 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: front-end name: front-end spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: gcr.io/google-samples/gb-frontend:v4 name: v4 referencePolicy: type: "" --- apiVersion: v1 kind: Route metadata: labels: io.kompose.service: front-end name: front-end spec: host: lb port: targetPort: 80 to: kind: Service name: front-end ================================================ FILE: script/test/fixtures/fsgroup/compose.yaml ================================================ volumes: pgadmin-data: services: pgadmin: labels: kompose.security-context.fsgroup: 1001 image: dpage/pgadmin4 environment: PGADMIN_DEFAULT_EMAIL: dumb_pgadmin_user@email.com PGADMIN_DEFAULT_PASSWORD: pgadmin_password volumes: - pgadmin-data:/var/lib/pgadmin ================================================ FILE: script/test/fixtures/fsgroup/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: pgadmin name: pgadmin spec: replicas: 1 selector: matchLabels: io.kompose.service: pgadmin strategy: type: Recreate template: metadata: labels: io.kompose.service: pgadmin spec: containers: - env: - name: PGADMIN_DEFAULT_EMAIL value: dumb_pgadmin_user@email.com - name: PGADMIN_DEFAULT_PASSWORD value: pgadmin_password image: dpage/pgadmin4 name: pgadmin volumeMounts: - mountPath: /var/lib/pgadmin name: pgadmin-data restartPolicy: Always securityContext: fsGroup: 1001 volumes: - name: pgadmin-data persistentVolumeClaim: claimName: pgadmin-data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: pgadmin-data name: pgadmin-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/fsgroup/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: pgadmin name: pgadmin spec: replicas: 1 selector: io.kompose.service: pgadmin strategy: type: Recreate template: metadata: labels: io.kompose.service: pgadmin spec: containers: - env: - name: PGADMIN_DEFAULT_EMAIL value: dumb_pgadmin_user@email.com - name: PGADMIN_DEFAULT_PASSWORD value: pgadmin_password image: ' ' name: pgadmin volumeMounts: - mountPath: /var/lib/pgadmin name: pgadmin-data restartPolicy: Always securityContext: fsGroup: 1001 volumes: - name: pgadmin-data persistentVolumeClaim: claimName: pgadmin-data test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - pgadmin from: kind: ImageStreamTag name: pgadmin:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: pgadmin name: pgadmin spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: dpage/pgadmin4 name: latest referencePolicy: type: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: pgadmin-data name: pgadmin-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/gitlab/README.md ================================================ ## Gitlab Gitlab, Postgresql, Redis ### Usage The simplest thing to do: ```bash export $(cat envs) docker-compose up ``` To customize the values edit `envs` file. ================================================ FILE: script/test/fixtures/gitlab/docker-compose.yml ================================================ services: postgresql: image: swordphilic/postgresql ports: - "$DB_PORT" environment: DB_NAME: $DB_NAME DB_PASS: $DB_PASS DB_USER: $DB_USER gitlab: image: swordphilic/gitlab ports: - "30000:80" - "30001:443" - "30002:22" restart: always environment: DB_TYPE: postgres DB_HOST: postgresql DB_PORT: $DB_PORT DB_NAME: $DB_NAME DB_PASS: $DB_PASS DB_USER: $DB_USER REDIS_HOST: redis REDIS_PORT: $REDIS_PORT redis: image: swordphilic/redis ports: - $REDIS_PORT ================================================ FILE: script/test/fixtures/gitlab/envs ================================================ DB_PORT=5432 DB_NAME=gitlab DB_PASS=gitlab DB_USER=gitlab REDIS_PORT=6379 ================================================ FILE: script/test/fixtures/gitlab/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "30000", "port": 30000, "targetPort": 80 }, { "name": "30001", "port": 30001, "targetPort": 443 }, { "name": "30002", "port": 30002, "targetPort": 22 } ], "selector": { "io.kompose.service": "gitlab" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "name": "gitlab" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "gitlab" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "containers": [ { "env": [ { "name": "DB_HOST", "value": "postgresql" }, { "name": "DB_NAME", "value": "gitlab" }, { "name": "DB_PASS", "value": "gitlab" }, { "name": "DB_PORT", "value": "5432" }, { "name": "DB_TYPE", "value": "postgres" }, { "name": "DB_USER", "value": "gitlab" }, { "name": "REDIS_HOST", "value": "redis" }, { "name": "REDIS_PORT", "value": "6379" } ], "image": "swordphilic/gitlab", "imagePullPolicy": "", "name": "gitlab", "ports": [ { "containerPort": 80 }, { "containerPort": 443 }, { "containerPort": 22 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "name": "postgresql" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "postgresql" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "containers": [ { "env": [ { "name": "DB_NAME", "value": "gitlab" }, { "name": "DB_PASS", "value": "gitlab" }, { "name": "DB_USER", "value": "gitlab" } ], "image": "swordphilic/postgresql", "imagePullPolicy": "", "name": "postgresql", "ports": [ { "containerPort": 5432 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "swordphilic/redis", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/gitlab/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "30000", "port": 30000, "targetPort": 80 }, { "name": "30001", "port": 30001, "targetPort": 443 }, { "name": "30002", "port": 30002, "targetPort": 22 } ], "selector": { "io.kompose.service": "gitlab" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "gitlab" ], "from": { "kind": "ImageStreamTag", "name": "gitlab:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "gitlab" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "containers": [ { "name": "gitlab", "image": " ", "ports": [ { "containerPort": 80 }, { "containerPort": 443 }, { "containerPort": 22 } ], "env": [ { "name": "DB_HOST", "value": "postgresql" }, { "name": "DB_NAME", "value": "gitlab" }, { "name": "DB_PASS", "value": "gitlab" }, { "name": "DB_PORT", "value": "5432" }, { "name": "DB_TYPE", "value": "postgres" }, { "name": "DB_USER", "value": "gitlab" }, { "name": "REDIS_HOST", "value": "redis" }, { "name": "REDIS_PORT", "value": "6379" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "gitlab", "creationTimestamp": null, "labels": { "io.kompose.service": "gitlab" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "swordphilic/gitlab" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "postgresql" ], "from": { "kind": "ImageStreamTag", "name": "postgresql:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "postgresql" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "containers": [ { "name": "postgresql", "image": " ", "ports": [ { "containerPort": 5432 } ], "env": [ { "name": "DB_NAME", "value": "gitlab" }, { "name": "DB_PASS", "value": "gitlab" }, { "name": "DB_USER", "value": "gitlab" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "swordphilic/postgresql" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "swordphilic/redis" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/healthcheck/compose-healthcheck.yaml ================================================ services: # test exec redis: image: redis ports: - "6379" healthcheck: test: echo "liveness" interval: 10s timeout: 1s retries: 5 labels: kompose.service.healthcheck.readiness.test: echo "liveness" kompose.service.healthcheck.readiness.interval: 10s kompose.service.healthcheck.readiness.timeout: 1s kompose.service.healthcheck.readiness.retries: 5 # test http get postgresql: image: postgresql ports: - "5432" healthcheck: interval: 10s timeout: 1s retries: 5 labels: kompose.service.healthcheck.liveness.http_get_path: /health kompose.service.healthcheck.liveness.http_get_port: 8080 kompose.service.healthcheck.readiness.http_get_path: /ready kompose.service.healthcheck.readiness.http_get_port: 8080 kompose.service.healthcheck.readiness.interval: 10s kompose.service.healthcheck.readiness.timeout: 1s kompose.service.healthcheck.readiness.retries: 5 # test tcp socket mongo: image: mongo ports: - "27017" healthcheck: interval: 10s timeout: 1s retries: 5 labels: kompose.service.group: "my-group" kompose.service.healthcheck.liveness.tcp_port: 8080 kompose.service.healthcheck.readiness.tcp_port: 9090 kompose.service.healthcheck.readiness.interval: 10s kompose.service.healthcheck.readiness.timeout: 1s kompose.service.healthcheck.readiness.retries: 5 # test multiple service merge mysql: image: mysql ports: - "3306" healthcheck: interval: 11s timeout: 2s retries: 6 labels: kompose.service.group: "my-group" kompose.service.healthcheck.liveness.tcp_port: 8081 kompose.service.healthcheck.readiness.tcp_port: 9091 kompose.service.healthcheck.readiness.interval: 11s kompose.service.healthcheck.readiness.timeout: 2s kompose.service.healthcheck.readiness.retries: 6 ================================================ FILE: script/test/fixtures/healthcheck/output-healthcheck-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: mongo name: mongo spec: ports: - name: "27017" port: 27017 targetPort: 27017 selector: io.kompose.service: mongo --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: mysql name: mysql spec: ports: - name: "3306" port: 3306 targetPort: 3306 selector: io.kompose.service: mysql --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: postgresql name: postgresql spec: ports: - name: "5432" port: 5432 targetPort: 5432 selector: io.kompose.service: postgresql --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: mongo name: mongo spec: replicas: 1 selector: matchLabels: io.kompose.service: mongo template: metadata: labels: io.kompose.service: mongo spec: containers: - image: mongo livenessProbe: failureThreshold: 5 periodSeconds: 10 tcpSocket: port: 8080 timeoutSeconds: 1 name: mongo ports: - containerPort: 27017 protocol: TCP readinessProbe: failureThreshold: 5 periodSeconds: 10 tcpSocket: port: 9090 timeoutSeconds: 1 restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: mysql name: mysql spec: replicas: 1 selector: matchLabels: io.kompose.service: mysql template: metadata: labels: io.kompose.service: mysql spec: containers: - image: mysql livenessProbe: failureThreshold: 6 periodSeconds: 11 tcpSocket: port: 8081 timeoutSeconds: 2 name: mysql ports: - containerPort: 3306 protocol: TCP readinessProbe: failureThreshold: 6 periodSeconds: 11 tcpSocket: port: 9091 timeoutSeconds: 2 restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: postgresql name: postgresql spec: replicas: 1 selector: matchLabels: io.kompose.service: postgresql template: metadata: labels: io.kompose.service: postgresql spec: containers: - image: postgresql livenessProbe: failureThreshold: 5 httpGet: path: /health port: 8080 periodSeconds: 10 timeoutSeconds: 1 name: postgresql ports: - containerPort: 5432 protocol: TCP readinessProbe: failureThreshold: 5 httpGet: path: /ready port: 8080 periodSeconds: 10 timeoutSeconds: 1 restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis livenessProbe: exec: command: - echo "liveness" failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 name: redis ports: - containerPort: 6379 protocol: TCP readinessProbe: exec: command: - echo - liveness failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 restartPolicy: Always ================================================ FILE: script/test/fixtures/healthcheck/output-healthcheck-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: mongo name: mongo spec: ports: - name: "27017" port: 27017 targetPort: 27017 selector: io.kompose.service: mongo --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: mysql name: mysql spec: ports: - name: "3306" port: 3306 targetPort: 3306 selector: io.kompose.service: mysql --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: postgresql name: postgresql spec: ports: - name: "5432" port: 5432 targetPort: 5432 selector: io.kompose.service: postgresql --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: mongo name: mongo spec: replicas: 1 selector: io.kompose.service: mongo template: metadata: labels: io.kompose.service: mongo spec: containers: - image: ' ' livenessProbe: failureThreshold: 5 periodSeconds: 10 tcpSocket: port: 8080 timeoutSeconds: 1 name: mongo ports: - containerPort: 27017 protocol: TCP readinessProbe: failureThreshold: 5 periodSeconds: 10 tcpSocket: port: 9090 timeoutSeconds: 1 restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - mongo from: kind: ImageStreamTag name: mongo:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: mongo name: mongo spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mongo name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: mysql name: mysql spec: replicas: 1 selector: io.kompose.service: mysql template: metadata: labels: io.kompose.service: mysql spec: containers: - image: ' ' livenessProbe: failureThreshold: 6 periodSeconds: 11 tcpSocket: port: 8081 timeoutSeconds: 2 name: mysql ports: - containerPort: 3306 protocol: TCP readinessProbe: failureThreshold: 6 periodSeconds: 11 tcpSocket: port: 9091 timeoutSeconds: 2 restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - mysql from: kind: ImageStreamTag name: mysql:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: mysql name: mysql spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mysql name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: postgresql name: postgresql spec: replicas: 1 selector: io.kompose.service: postgresql template: metadata: labels: io.kompose.service: postgresql spec: containers: - image: ' ' livenessProbe: failureThreshold: 5 httpGet: path: /health port: 8080 periodSeconds: 10 timeoutSeconds: 1 name: postgresql ports: - containerPort: 5432 protocol: TCP readinessProbe: failureThreshold: 5 httpGet: path: /ready port: 8080 periodSeconds: 10 timeoutSeconds: 1 restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - postgresql from: kind: ImageStreamTag name: postgresql:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: postgresql name: postgresql spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: postgresql name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' livenessProbe: exec: command: - echo "liveness" failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 name: redis ports: - containerPort: 6379 protocol: TCP readinessProbe: exec: command: - echo - liveness failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/host-port-protocol/compose.yaml ================================================ services: nginx: labels: kompose.container.kompose.controller.port.expose: true ports: - target: 80 published: 80 protocol: tcp mode: host image: nginx ================================================ FILE: script/test/fixtures/host-port-protocol/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: nginx name: nginx spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: nginx --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: nginx name: nginx spec: replicas: 1 selector: matchLabels: io.kompose.service: nginx template: metadata: labels: io.kompose.service: nginx spec: containers: - image: nginx name: nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/host-port-protocol/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: nginx name: nginx spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: nginx --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: nginx name: nginx spec: replicas: 1 selector: io.kompose.service: nginx template: metadata: labels: io.kompose.service: nginx spec: containers: - image: ' ' name: nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - nginx from: kind: ImageStreamTag name: nginx:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: nginx name: nginx spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/hpa/compose.yaml ================================================ services: web: image: nginx labels: kompose.hpa.cpu: 50 kompose.hpa.memory: 70 kompose.hpa.replicas.min: 1 kompose.hpa.replicas.max: 10 ================================================ FILE: script/test/fixtures/hpa/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web restartPolicy: Always --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web spec: maxReplicas: 10 metrics: - resource: name: cpu target: averageUtilization: 50 type: Utilization type: Resource - resource: name: memory target: averageUtilization: 70 type: Utilization type: Resource minReplicas: 1 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web ================================================ FILE: script/test/fixtures/image-pull-policy/compose-files/v12-fail-image-pull-policy.yml ================================================ services: nginx0: image: nginx labels: kompose.image-pull-policy: "Fail" ================================================ FILE: script/test/fixtures/image-pull-policy/compose-files/v12-image-pull-policy.yml ================================================ services: nginx0: image: nginx labels: kompose.image-pull-policy: "Always" ================================================ FILE: script/test/fixtures/image-pull-policy/compose-files/v3-image-pull-policy.yml ================================================ services: nginx0: image: nginx labels: kompose.image-pull-policy: "Always" nginx1: image: nginx labels: kompose.image-pull-policy: "IfNotPresent" nginx2: image: nginx labels: kompose.image-pull-policy: "Never" ================================================ FILE: script/test/fixtures/image-pull-policy/provider-files/kubernetes-v12-image-pull-policy.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Always", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx0" }, "name": "nginx0" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx0" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Always", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx0" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "Always", "name": "nginx0", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/image-pull-policy/provider-files/kubernetes-v3-image-pull-policy.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Always", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx0" }, "name": "nginx0" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx0" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Always", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx0" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "Always", "name": "nginx0", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "IfNotPresent", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx1" }, "name": "nginx1" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx1" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "IfNotPresent", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx1" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "IfNotPresent", "name": "nginx1", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Never", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx2" }, "name": "nginx2" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx2" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-policy": "Never", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx2" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "Never", "name": "nginx2", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/image-pull-secret/compose-files/docker-compose-image-pull-secret.yml ================================================ tm-image-service: image: premium/private-image labels: kompose.image-pull-secret: "sample-k8s-secret-name" open-image-service: image: nginx-alpine ================================================ FILE: script/test/fixtures/image-pull-secret/provider-files/kubernetes-image-pull-secret.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "open-image-service" }, "name": "open-image-service" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "open-image-service" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "open-image-service" } }, "spec": { "containers": [ { "image": "nginx-alpine", "imagePullPolicy": "", "name": "open-image-service", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-secret": "sample-k8s-secret-name", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "tm-image-service" }, "name": "tm-image-service" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "tm-image-service" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.image-pull-secret": "sample-k8s-secret-name", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "tm-image-service" } }, "spec": { "containers": [ { "image": "premium/private-image", "imagePullPolicy": "", "name": "tm-image-service", "resources": {} } ], "imagePullSecrets": [ { "name": "sample-k8s-secret-name" } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/initcontainer/compose.yaml ================================================ services: web: image: nginx labels: kompose.init.containers.name: "init-myservice" kompose.init.containers.image: "busybox:1.28" kompose.init.containers.command: '["sh", "-c", "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]' ================================================ FILE: script/test/fixtures/initcontainer/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web initContainers: - command: - sh - -c - until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done image: busybox:1.28 name: init-myservice restartPolicy: Always ================================================ FILE: script/test/fixtures/keyonly-envs/env.yml ================================================ services: redis-master: image: registry.k8s.io/redis:e2e ports: - "6379" redis-replica: image: registry.k8s.io/redis-slave:v2 ports: - "6379" environment: - GET_HOSTS_FROM=dns - RACK_ENV=development - SHOW=true - SESSION_SECRET frontend: image: gcr.io/google-samples/gb-frontend:v4 ports: - "80:80" environment: GET_HOSTS_FROM: dns RACK_ENV: development SHOW: 'true' SESSION_SECRET: ================================================ FILE: script/test/fixtures/keyonly-envs/envs ================================================ SESSION_SECRET=session ================================================ FILE: script/test/fixtures/keyonly-envs/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "name": "frontend" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "frontend" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" }, { "name": "RACK_ENV", "value": "development" }, { "name": "SESSION_SECRET", "value": "session" }, { "name": "SHOW", "value": "true" } ], "image": "gcr.io/google-samples/gb-frontend:v4", "imagePullPolicy": "", "name": "frontend", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-master" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "name": "redis-replica" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-replica" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" }, { "name": "RACK_ENV", "value": "development" }, { "name": "SESSION_SECRET", "value": "session" }, { "name": "SHOW", "value": "true" } ], "image": "registry.k8s.io/redis-slave:v2", "imagePullPolicy": "", "name": "redis-replica", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/label/compose.yaml ================================================ services: app: image: node:18-alpine ports: - 3000:3000 labels: - "com.example.label=foo" ================================================ FILE: script/test/fixtures/label/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: annotations: com.example.label: foo labels: io.kompose.service: app name: app spec: ports: - name: "3000" port: 3000 targetPort: 3000 selector: io.kompose.service: app --- apiVersion: apps/v1 kind: Deployment metadata: annotations: com.example.label: foo labels: io.kompose.service: app name: app spec: replicas: 1 selector: matchLabels: io.kompose.service: app template: metadata: annotations: com.example.label: foo labels: io.kompose.service: app spec: containers: - image: node:18-alpine name: app ports: - containerPort: 3000 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/label-port/docker-compose.yml ================================================ services: webapiapplication: image: webapiapplication labels: kompose.service.type: NodePort ================================================ FILE: script/test/fixtures/multiple-files/first.yaml ================================================ services: foo: image: foo deploy: replicas: 3 bar: image: bar ================================================ FILE: script/test/fixtures/multiple-files/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: bar name: bar spec: replicas: 99 selector: matchLabels: io.kompose.service: bar template: metadata: labels: io.kompose.service: bar spec: containers: - image: bar name: bar restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: foo name: foo spec: replicas: 3 selector: matchLabels: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - image: foo name: foo restartPolicy: Always ================================================ FILE: script/test/fixtures/multiple-files/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: bar name: bar spec: replicas: 99 selector: io.kompose.service: bar template: metadata: labels: io.kompose.service: bar spec: containers: - image: ' ' name: bar restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - bar from: kind: ImageStreamTag name: bar:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: bar name: bar spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: bar name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: foo name: foo spec: replicas: 3 selector: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - image: ' ' name: foo restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - foo from: kind: ImageStreamTag name: foo:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: foo name: foo spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: foo name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/multiple-files/second.yaml ================================================ services: bar: deploy: replicas: 99 ================================================ FILE: script/test/fixtures/multiple-type-volumes/compose.yaml ================================================ services: web: image: nginx volumes: - ./tls:/etc/tls - ./tls/a.key:/etc/test-a-key.key labels: kompose.volume.type: configMap db: image: mysql volumes: - db-data:/var/lib/mysql labels: kompose.volume.type: persistentVolumeClaim volumes: db-data: ================================================ FILE: script/test/fixtures/multiple-type-volumes/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: mysql name: db volumeMounts: - mountPath: /var/lib/mysql name: db-data restartPolicy: Always volumes: - name: db-data persistentVolumeClaim: claimName: db-data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: db-data name: db-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/multiple-type-volumes/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: ' ' name: db volumeMounts: - mountPath: /var/lib/mysql name: db-data restartPolicy: Always volumes: - name: db-data persistentVolumeClaim: claimName: db-data test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - db from: kind: ImageStreamTag name: db:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: db name: db spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mysql name: latest referencePolicy: type: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: db-data name: db-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web spec: replicas: 1 selector: io.kompose.service: web strategy: type: Recreate template: metadata: labels: io.kompose.service: web spec: containers: - image: ' ' name: web volumeMounts: - mountPath: /etc/tls name: web-cm0 - mountPath: /etc/test-a-key.key name: web-cm1 subPath: test-a-key.key restartPolicy: Always volumes: - configMap: name: web-cm0 name: web-cm0 - configMap: items: - key: a.key path: test-a-key.key name: web-cm1 name: web-cm1 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx name: latest referencePolicy: type: "" --- apiVersion: v1 data: a.crt: test-crt-data... a.key: test-key-data.... kind: ConfigMap metadata: labels: io.kompose.service: web name: web-cm0 --- apiVersion: v1 data: a.key: test-key-data.... kind: ConfigMap metadata: annotations: use-subpath: "true" labels: io.kompose.service: web name: web-cm1 ================================================ FILE: script/test/fixtures/multiple-type-volumes/tls/a.crt ================================================ test-crt-data... ================================================ FILE: script/test/fixtures/multiple-type-volumes/tls/a.key ================================================ test-key-data.... ================================================ FILE: script/test/fixtures/namespace/compose.yaml ================================================ services: web: image: nginx ports: - 80:80 ================================================ FILE: script/test/fixtures/namespace/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web namespace: web spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: web --- apiVersion: v1 kind: Namespace metadata: name: web namespace: web --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: web name: web namespace: web spec: replicas: 1 selector: matchLabels: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: nginx name: web ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/namespace/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: web name: web namespace: web spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: web --- apiVersion: v1 kind: Namespace metadata: name: web namespace: web --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: web name: web namespace: web spec: replicas: 1 selector: io.kompose.service: web template: metadata: labels: io.kompose.service: web spec: containers: - image: ' ' name: web ports: - containerPort: 80 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - web from: kind: ImageStreamTag name: web:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: web name: web namespace: web spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/network/compose-v3.yaml ================================================ networks: app: external: name: app-network web: external: name: web-network services: appFoo: image: foo:latest command: sh -c "echo Hello Foo" networks: app: {} web: {} ================================================ FILE: script/test/fixtures/network/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "appfoo" }, "name": "appfoo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "appfoo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.network/app-network": "true", "io.kompose.network/web-network": "true", "io.kompose.service": "appfoo" } }, "spec": { "containers": [ { "args": [ "sh", "-c", "echo Hello Foo" ], "image": "foo:latest", "imagePullPolicy": "", "name": "appfoo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "app-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/app-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/app-network": "true" } } } ] } ] } }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/web-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/web-network": "true" } } } ] } ] } } ] } ================================================ FILE: script/test/fixtures/network/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "appfoo", "creationTimestamp": null, "labels": { "io.kompose.service": "appfoo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "appfoo" ], "from": { "kind": "ImageStreamTag", "name": "appfoo:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "appfoo" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.network/app-network": "true", "io.kompose.network/web-network": "true", "io.kompose.service": "appfoo" } }, "spec": { "containers": [ { "name": "appfoo", "image": " ", "args": [ "sh", "-c", "echo Hello Foo" ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "appfoo", "creationTimestamp": null, "labels": { "io.kompose.service": "appfoo" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "foo:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/network-mode-service/compose.yaml ================================================ services: client: image: busybox command: ['sleep', 'infinity'] container_name: threats-client restart: unless-stopped ports: - '8080:8080' server: image: busybox command: ['sleep', 'infinity'] container_name: threats-server restart: unless-stopped network_mode: service:client ================================================ FILE: script/test/fixtures/network-mode-service/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: client name: client spec: ports: - name: "8080" port: 8080 targetPort: 8080 selector: io.kompose.service: client --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: client name: client spec: replicas: 1 selector: matchLabels: io.kompose.service: client template: metadata: labels: io.kompose.service: client spec: containers: - args: - sleep - infinity image: busybox name: threats-client ports: - containerPort: 8080 protocol: TCP - args: - sleep - infinity image: busybox name: threats-server restartPolicy: Always ================================================ FILE: script/test/fixtures/network-policies/compose.yaml ================================================ networks: web: services: nginx: image: nginx ports: - 80:80 networks: - web ================================================ FILE: script/test/fixtures/network-policies/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: nginx name: nginx spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: nginx --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: nginx name: nginx spec: replicas: 1 selector: matchLabels: io.kompose.service: nginx template: metadata: labels: io.kompose.network/network-policies-web: "true" io.kompose.service: nginx spec: containers: - image: nginx name: nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: network-policies-web spec: ingress: - from: - podSelector: matchLabels: io.kompose.network/network-policies-web: "true" podSelector: matchLabels: io.kompose.network/network-policies-web: "true" ================================================ FILE: script/test/fixtures/nginx-node-redis/README.md ================================================ ### Usage Ref: http://anandmanisankar.com/posts/docker-container-nginx-node-redis-example/ ```bash docker-compose up ``` ================================================ FILE: script/test/fixtures/nginx-node-redis/compose-v3.yaml ================================================ services: nginx: build: ./nginx ports: - "80:80" restart: always node1: build: ./node ports: - "8080" node2: build: ./node ports: - "8080" node3: build: ./node ports: - "8080" redis: image: redis ports: - "6379" ================================================ FILE: script/test/fixtures/nginx-node-redis/compose.yaml ================================================ services: nginx: build: ./nginx ports: - "80:80" restart: always node1: build: ./node ports: - "8080" node2: build: ./node ports: - "8080" node3: build: ./node ports: - "8080" redis: image: redis ports: - "6379" ================================================ FILE: script/test/fixtures/nginx-node-redis/nginx/Dockerfile ================================================ # Set nginx base image FROM nginx # File Author / Maintainer MAINTAINER Anand Mani Sankar # Copy custom configuration file from the current directory COPY nginx.conf /etc/nginx/nginx.conf ================================================ FILE: script/test/fixtures/nginx-node-redis/nginx/nginx.conf ================================================ worker_processes 4; events { worker_connections 1024; } http { upstream node-app { least_conn; server node1:8080 weight=60 max_fails=10 fail_timeout=60s; server node2:8080 weight=60 max_fails=10 fail_timeout=60s; server node3:8080 weight=60 max_fails=10 fail_timeout=60s; } server { listen 80; location / { proxy_pass http://node-app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } } ================================================ FILE: script/test/fixtures/nginx-node-redis/node/Dockerfile ================================================ FROM centos:7 RUN yum -y install epel-release && yum install -y nodejs npm gcc* make RUN /bin/bash -c 'npm install -g nodemon' && mkdir /src # Define working directory WORKDIR /src ADD . /src RUN cd /src && npm install # Expose port EXPOSE 8080 # Run app using nodemon CMD /bin/bash -c 'nodemon /src/index.js' ================================================ FILE: script/test/fixtures/nginx-node-redis/node/index.js ================================================ var express = require('express'), http = require('http'), redis = require('redis'); var app = express(); console.log(process.env.REDIS_PORT_6379_TCP_ADDR + ':' + process.env.REDIS_PORT_6379_TCP_PORT); // APPROACH 1: Using environment variables created by Docker // var client = redis.createClient( // process.env.REDIS_PORT_6379_TCP_PORT, // process.env.REDIS_PORT_6379_TCP_ADDR // ); // APPROACH 2: Using host entries created by Docker in /etc/hosts (RECOMMENDED) var client = redis.createClient('6379', 'redis'); app.get('/', function(req, res, next) { client.incr('counter', function(err, counter) { if(err) return next(err); res.send('This page has been viewed ' + counter + ' times!'); }); }); http.createServer(app).listen(process.env.PORT || 8080, function() { console.log('Listening on port ' + (process.env.PORT || 8080)); }); ================================================ FILE: script/test/fixtures/nginx-node-redis/node/package.json ================================================ { "name": "node", "version": "1.0.0", "description": "", "main": "index.js", "author": "Anand Mani Sankar", "license": "ISC", "dependencies": { "express": "^4.12.3", "hiredis": "^0.2.0", "mocha": "^2.2.1", "redis": "^0.12.1" } } ================================================ FILE: script/test/fixtures/nginx-node-redis/node/test/dummyTest.js ================================================ var assert = require("assert"); describe('Dummy Test', function(){ it('should pass', function(){ assert.ok(true, "It is true!"); }); }); ================================================ FILE: script/test/fixtures/nginx-node-redis/output-k8s-template-v3.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node1" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node2" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node3" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "name": "nginx" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "nginx", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "name": "node1" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node1" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "containers": [ { "image": "node1", "imagePullPolicy": "", "name": "node1", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "name": "node2" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node2" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "containers": [ { "image": "node2", "imagePullPolicy": "", "name": "node2", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "name": "node3" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node3" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "containers": [ { "image": "node3", "imagePullPolicy": "", "name": "node3", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/nginx-node-redis/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node1" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node2" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node3" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "name": "nginx" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "nginx", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "name": "node1" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node1" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "containers": [ { "image": "node1", "imagePullPolicy": "", "name": "node1", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "name": "node2" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node2" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "containers": [ { "image": "node2", "imagePullPolicy": "", "name": "node2", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "name": "node3" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "node3" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "containers": [ { "image": "node3", "imagePullPolicy": "", "name": "node3", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/nginx-node-redis/output-os-template-v3.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node1" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node2" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node3" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "nginx" ], "from": { "kind": "ImageStreamTag", "name": "nginx:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "nginx" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/nginx/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "nginx:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node1" ], "from": { "kind": "ImageStreamTag", "name": "node1:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node1" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "containers": [ { "name": "node1", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node1:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node2" ], "from": { "kind": "ImageStreamTag", "name": "node2:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node2" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "containers": [ { "name": "node2", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node2:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node3" ], "from": { "kind": "ImageStreamTag", "name": "node3:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node3" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "containers": [ { "name": "node3", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node3" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node3:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": {}, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/nginx-node-redis/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node1" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node2" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node3" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "nginx" ], "from": { "kind": "ImageStreamTag", "name": "nginx:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "nginx" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/nginx/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "nginx:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node1" ], "from": { "kind": "ImageStreamTag", "name": "node1:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node1" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "containers": [ { "name": "node1", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node1:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node2" ], "from": { "kind": "ImageStreamTag", "name": "node2:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node2" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "containers": [ { "name": "node2", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node2:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node3" ], "from": { "kind": "ImageStreamTag", "name": "node3:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node3" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "containers": [ { "name": "node3", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node3" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "BuildConfig", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "triggers": [ { "type": "ConfigChange" } ], "runPolicy": "Serial", "source": { "type": "Git", "git": { "uri": "%URI%", "ref": "%REF%" }, "contextDir": "script/test/fixtures/nginx-node-redis/node/" }, "strategy": { "type": "Docker", "dockerStrategy": {} }, "output": { "to": { "kind": "ImageStreamTag", "name": "node3:latest" } }, "resources": {}, "postCommit": {}, "affinity": null }, "status": { "lastVersion": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": {}, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/nginx-node-redis/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node1" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node2" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "ports": [ { "name": "8080", "port": 8080, "targetPort": 8080 } ], "selector": { "io.kompose.service": "node3" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "nginx" ], "from": { "kind": "ImageStreamTag", "name": "nginx:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "nginx" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node1" ], "from": { "kind": "ImageStreamTag", "name": "node1:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node1" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "containers": [ { "name": "node1", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node1", "creationTimestamp": null, "labels": { "io.kompose.service": "node1" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node2" ], "from": { "kind": "ImageStreamTag", "name": "node2:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node2" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "containers": [ { "name": "node2", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node2", "creationTimestamp": null, "labels": { "io.kompose.service": "node2" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "node3" ], "from": { "kind": "ImageStreamTag", "name": "node3:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "node3" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "containers": [ { "name": "node3", "image": " ", "ports": [ { "containerPort": 8080 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "node3", "creationTimestamp": null, "labels": { "io.kompose.service": "node3" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "node3" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/no-profile-warning/compose.yaml ================================================ services: web: image: nginx profiles: - test ports: - 80:80 ================================================ FILE: script/test/fixtures/ports-with-ip/docker-compose.yml ================================================ services: web: image: tuna/docker-counter23 ports: - "127.0.0.1:5000:5000/tcp" links: - redis networks: - default redis: image: redis:3.0 networks: - default ports: - "6379/tcp" - "1234:1235/udp" ================================================ FILE: script/test/fixtures/ports-with-ip/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 }, { "name": "1234", "protocol": "UDP", "port": 1234, "targetPort": 1235 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 }, { "containerPort": 1235, "protocol": "UDP" } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000, "hostIP": "127.0.0.1" } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/ports-with-ip/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 }, { "name": "1234", "protocol": "UDP", "port": 1234, "targetPort": 1235 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": "redis:3.0", "ports": [ { "containerPort": 6379 }, { "containerPort": 1235, "protocol": "UDP" } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": "tuna/docker-counter23", "ports": [ { "containerPort": 5000, "hostIP": "127.0.0.1" } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} } ] } ================================================ FILE: script/test/fixtures/pvc-request-size/compose.yaml ================================================ services: redis: restart: always image: sameersbn/redis:latest command: - --loglevel warning volumes: - /srv/docker/gitlab/redis:/var/lib/redis ports: - "6379" postgresql: restart: always image: sameersbn/postgresql:9.5-3 volumes: - /srv/docker/gitlab/postgresql:/var/lib/postgresql environment: - DB_USER=gitlab - DB_PASS=password - DB_NAME=gitlabhq_production - DB_EXTENSION=pg_trgm ports: - "5432" labels: kompose.volume.size: 200Mi ================================================ FILE: script/test/fixtures/pvc-request-size/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "postgresql" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" } }, "spec": { "volumes": [ { "name": "postgresql-claim0", "persistentVolumeClaim": { "claimName": "postgresql-claim0" } } ], "containers": [ { "name": "postgresql", "image": "sameersbn/postgresql:9.5-3", "ports": [ { "containerPort": 5432 } ], "env": [ { "name": "DB_EXTENSION", "value": "pg_trgm" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_USER", "value": "gitlab" } ], "resources": {}, "volumeMounts": [ { "name": "postgresql-claim0", "mountPath": "/var/lib/postgresql" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "postgresql-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "200Mi" } } }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "volumes": [ { "name": "redis-claim0", "persistentVolumeClaim": { "claimName": "redis-claim0" } } ], "containers": [ { "name": "redis", "image": "sameersbn/redis:latest", "args": [ "--loglevel warning" ], "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "name": "redis-claim0", "mountPath": "/var/lib/redis" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "redis-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "300Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/pvc-request-size/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.volume.size": "200Mi" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "postgresql" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" }, "annotations": { "kompose.volume.size": "200Mi" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "postgresql" ], "from": { "kind": "ImageStreamTag", "name": "postgresql:9.5-3" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "postgresql" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "volumes": [ { "name": "postgresql-claim0", "persistentVolumeClaim": { "claimName": "postgresql-claim0" } } ], "containers": [ { "name": "postgresql", "image": " ", "ports": [ { "containerPort": 5432 } ], "env": [ { "name": "DB_EXTENSION", "value": "pg_trgm" }, { "name": "DB_NAME", "value": "gitlabhq_production" }, { "name": "DB_PASS", "value": "password" }, { "name": "DB_USER", "value": "gitlab" } ], "resources": {}, "volumeMounts": [ { "name": "postgresql-claim0", "mountPath": "/var/lib/postgresql" } ] } ], "restartPolicy": "Always" } } }, "status": { "latestVersion": 0, "observedGeneration": 0, "replicas": 0, "updatedReplicas": 0, "availableReplicas": 0, "unavailableReplicas": 0 } }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "postgresql", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql" } }, "spec": { "lookupPolicy": { "local": false }, "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "sameersbn/postgresql:9.5-3" }, "generation": null, "importPolicy": {}, "referencePolicy": { "type": "" } } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "postgresql-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "postgresql-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "200Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "redis-claim0", "persistentVolumeClaim": { "claimName": "redis-claim0" } } ], "containers": [ { "name": "redis", "image": " ", "args": [ "--loglevel warning" ], "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "name": "redis-claim0", "mountPath": "/var/lib/redis" } ] } ], "restartPolicy": "Always" } } }, "status": { "latestVersion": 0, "observedGeneration": 0, "replicas": 0, "updatedReplicas": 0, "availableReplicas": 0, "unavailableReplicas": 0 } }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "lookupPolicy": { "local": false }, "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "sameersbn/redis:latest" }, "generation": null, "importPolicy": {}, "referencePolicy": { "type": "" } } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "redis-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "300Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/read-only/compose.yaml ================================================ services: test: image: alpine read_only: true ports: - 80:80 ================================================ FILE: script/test/fixtures/read-only/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: test name: test spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: test --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: test name: test spec: replicas: 1 selector: matchLabels: io.kompose.service: test template: metadata: labels: io.kompose.service: test spec: containers: - image: alpine name: test ports: - containerPort: 80 protocol: TCP securityContext: readOnlyRootFilesystem: true restartPolicy: Always ================================================ FILE: script/test/fixtures/read-only/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: test name: test spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: test --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: test name: test spec: replicas: 1 selector: io.kompose.service: test template: metadata: labels: io.kompose.service: test spec: containers: - image: ' ' name: test ports: - containerPort: 80 protocol: TCP securityContext: readOnlyRootFilesystem: true restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - test from: kind: ImageStreamTag name: test:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: test name: test spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: alpine name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/redis-example/compose.yaml ================================================ services: web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/resources-lowercase/compose.yaml ================================================ services: NGINX: image: nginx:latest ports: - 80:80 ================================================ FILE: script/test/fixtures/resources-lowercase/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: nginx name: nginx spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: nginx --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: nginx name: nginx spec: replicas: 1 selector: matchLabels: io.kompose.service: nginx template: metadata: labels: io.kompose.service: nginx spec: containers: - image: nginx:latest name: nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always ================================================ FILE: script/test/fixtures/resources-lowercase/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: nginx name: nginx spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: nginx --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: nginx name: nginx spec: replicas: 1 selector: io.kompose.service: nginx template: metadata: labels: io.kompose.service: nginx spec: containers: - image: ' ' name: nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - nginx from: kind: ImageStreamTag name: nginx:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: nginx name: nginx spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx:latest name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/secrets/docker-compose-secrets-long.yml ================================================ services: redis: image: redis:latest deploy: replicas: 1 secrets: - source: my_secret target: redis_secret uid: '103' gid: '103' mode: 0440 secrets: my_secret: file: ./my_secret.txt my_other_secret: external: true ================================================ FILE: script/test/fixtures/secrets/docker-compose-secrets-short.yml ================================================ services: redis: image: redis:latest deploy: replicas: 1 secrets: - my_secret - my_other_secret secrets: my_secret: file: ./my_secret.txt my_other_secret: external: true ================================================ FILE: script/test/fixtures/secrets/my_secret.txt ================================================ For the Watch! ================================================ FILE: script/test/fixtures/secrets/output-long-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Secret", "apiVersion": "v1", "metadata": { "name": "my_secret", "creationTimestamp": null, "labels": { "io.kompose.service": "my_secret" } }, "data": { "my_secret": "Rm9yIHRoZSBXYXRjaCE=" }, "type": "Opaque" }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/run/secrets/my_secret", "name": "my_secret" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "my_secret", "secret": { "defaultMode": 288, "items": [ { "key": "my_secret", "path": "redis_secret" } ], "secretName": "my_secret" } } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/secrets/output-long-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Secret", "apiVersion": "v1", "metadata": { "name": "my_secret", "creationTimestamp": null, "labels": { "io.kompose.service": "my_secret" } }, "data": { "my_secret": "Rm9yIHRoZSBXYXRjaCE=" }, "type": "Opaque" }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "my_secret", "secret": { "secretName": "my_secret", "items": [ { "key": "my_secret", "path": "redis_secret" } ], "defaultMode": 288 } } ], "containers": [ { "name": "redis", "image": " ", "resources": {}, "volumeMounts": [ { "name": "my_secret", "mountPath": "/run/secrets/my_secret" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/secrets/output-short-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Secret", "apiVersion": "v1", "metadata": { "name": "my_secret", "creationTimestamp": null, "labels": { "io.kompose.service": "my_secret" } }, "data": { "my_secret": "Rm9yIHRoZSBXYXRjaCE=" }, "type": "Opaque" }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:latest", "imagePullPolicy": "", "name": "redis", "resources": {}, "volumeMounts": [ { "mountPath": "/run/secrets/my_secret", "name": "my_secret" }, { "mountPath": "/run/secrets/my_other_secret", "name": "my_other_secret" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "my_secret", "secret": { "items": [ { "key": "my_secret", "path": "my_secret" } ], "secretName": "my_secret" } }, { "name": "my_other_secret", "secret": { "items": [ { "key": "my_other_secret", "path": "my_other_secret" } ], "secretName": "my_other_secret" } } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/secrets/output-short-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Secret", "apiVersion": "v1", "metadata": { "name": "my_secret", "creationTimestamp": null, "labels": { "io.kompose.service": "my_secret" } }, "data": { "my_secret": "Rm9yIHRoZSBXYXRjaCE=" }, "type": "Opaque" }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "volumes": [ { "name": "my_secret", "secret": { "secretName": "my_secret", "items": [ { "key": "my_secret", "path": "my_secret" } ] } }, { "name": "my_other_secret", "secret": { "secretName": "my_other_secret", "items": [ { "key": "my_other_secret", "path": "my_other_secret" } ] } } ], "containers": [ { "name": "redis", "image": " ", "resources": {}, "volumeMounts": [ { "name": "my_secret", "mountPath": "/run/secrets/my_secret" }, { "name": "my_other_secret", "mountPath": "/run/secrets/my_other_secret" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/security-contexts/compose.yaml ================================================ services: dind: container_name: dind image: docker:28.1.1-dind labels: kompose.service.group: cup ports: - 2375:2375 privileged: True cup: container_name: cup depends_on: - dind image: ghcr.io/sergi0g/cup:v3.4.0 labels: kompose.service.group: cup ports: - 8000:8000 ================================================ FILE: script/test/fixtures/security-contexts/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: cup name: cup spec: ports: - name: "8000" port: 8000 targetPort: 8000 selector: io.kompose.service: cup --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: cup name: dind spec: ports: - name: "2375" port: 2375 targetPort: 2375 selector: io.kompose.service: cup --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: cup name: cup spec: replicas: 1 selector: matchLabels: io.kompose.service: cup template: metadata: labels: io.kompose.service: cup spec: containers: - image: ghcr.io/sergi0g/cup:v3.4.0 name: cup ports: - containerPort: 8000 protocol: TCP - image: docker:28.1.1-dind name: dind ports: - containerPort: 2375 protocol: TCP securityContext: privileged: true restartPolicy: Always ================================================ FILE: script/test/fixtures/service-group/compose.yaml ================================================ services: librenms: image: librenms/librenms:latest container_name: librenms hostname: librenms ports: - target: 8000 published: 8000 protocol: tcp volumes: - "./librenms:/data" environment: - "TZ=${TZ}" restart: always dispatcher: image: librenms/dispatcher:latest container_name: dispatcher hostname: dispatcher depends_on: - librenms volumes: - "./librenms:/data" environment: - "TZ=${TZ}" restart: always ================================================ FILE: script/test/fixtures/service-group/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: librenms-dispatcher name: librenms spec: ports: - name: "8000" port: 8000 targetPort: 8000 selector: io.kompose.service: librenms-dispatcher --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: librenms-dispatcher name: librenms-dispatcher spec: replicas: 1 selector: matchLabels: io.kompose.service: librenms-dispatcher strategy: type: Recreate template: metadata: labels: io.kompose.service: librenms-dispatcher spec: containers: - env: - name: TZ image: librenms/dispatcher:latest name: dispatcher volumeMounts: - mountPath: /data name: librenms-dispatcher-claim0 - env: - name: TZ image: librenms/librenms:latest name: librenms ports: - containerPort: 8000 protocol: TCP volumeMounts: - mountPath: /data name: librenms-dispatcher-claim0 hostname: librenms restartPolicy: Always volumes: - name: librenms-dispatcher-claim0 persistentVolumeClaim: claimName: librenms-dispatcher-claim0 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: librenms-dispatcher-claim0 name: librenms-dispatcher-claim0 spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/service-label/docker-compose.yaml ================================================ services: mariadb: ports: - '3306:3306' labels: kompose.service.type: nodeport kompose.service.nodeport.port: "33111" image: 'bitnami/mariadb:latest' environment: - MARIADB_USER=bn_wordpress - MARIADB_DATABASE=bitnami_wordpress - ALLOW_EMPTY_PASSWORD=yes ================================================ FILE: script/test/fixtures/service-label/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.nodeport.port": "33111", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3306", "port": 3306, "targetPort": 3306, "nodePort": 33111 } ], "selector": { "io.kompose.service": "mariadb" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.nodeport.port": "33111", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "name": "mariadb" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "mariadb" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.nodeport.port": "33111", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "containers": [ { "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_DATABASE", "value": "bitnami_wordpress" }, { "name": "MARIADB_USER", "value": "bn_wordpress" } ], "image": "bitnami/mariadb:latest", "imagePullPolicy": "", "name": "mariadb", "ports": [ { "containerPort": 3306 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/service-label/output-oc.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.nodeport.port": "33111", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3306", "port": 3306, "targetPort": 3306, "nodePort": 33111 } ], "selector": { "io.kompose.service": "mariadb" }, "type": "NodePort" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.nodeport.port": "33111", "kompose.service.type": "nodeport", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "mariadb" ], "from": { "kind": "ImageStreamTag", "name": "mariadb:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "mariadb" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "containers": [ { "name": "mariadb", "image": " ", "ports": [ { "containerPort": 3306 } ], "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_DATABASE", "value": "bitnami_wordpress" }, { "name": "MARIADB_USER", "value": "bn_wordpress" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "bitnami/mariadb:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/service-name-change/docker-compose.yml ================================================ services: mariadb: labels: kompose.service.type: headless image: 'bitnami/mariadb:latest' volumes: - 'mariadb-data:/bitnami/mariadb' environment: - MARIADB_USER=bn_wordpress - MARIADB_DATABASE=bitnami_wordpress - ALLOW_EMPTY_PASSWORD=yes wordpress: image: 'bitnami/wordpress:latest' ports: - '80:80' - '443:443' volumes: - 'wordpress-data:/bitnami/wordpress' - 'apache-data:/bitnami/apache' - 'php-data:/bitnami/php' depends_on: - mariadb environment: - MARIADB_HOST=mariadb - MARIADB_PORT=3306 - WORDPRESS_DATABASE_USER=bn_wordpress - WORDPRESS_DATABASE_NAME=bitnami_wordpress - ALLOW_EMPTY_PASSWORD=yes volumes: mariadb-data: driver: local wordpress-data: driver: local apache-data: driver: local php-data: driver: local ================================================ FILE: script/test/fixtures/service-name-change/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "mariadb" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 }, { "name": "443", "port": 443, "targetPort": 443 } ], "selector": { "io.kompose.service": "wordpress" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "name": "mariadb" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "mariadb" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "containers": [ { "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_DATABASE", "value": "bitnami_wordpress" }, { "name": "MARIADB_USER", "value": "bn_wordpress" } ], "image": "bitnami/mariadb:latest", "imagePullPolicy": "", "name": "mariadb", "resources": {}, "volumeMounts": [ { "mountPath": "/bitnami/mariadb", "name": "servicenamechange-mariadb-data" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "servicenamechange-mariadb-data", "persistentVolumeClaim": { "claimName": "servicenamechange-mariadb-data" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-mariadb-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-mariadb-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "name": "wordpress" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "wordpress" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "containers": [ { "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_HOST", "value": "mariadb" }, { "name": "MARIADB_PORT", "value": "3306" }, { "name": "WORDPRESS_DATABASE_NAME", "value": "bitnami_wordpress" }, { "name": "WORDPRESS_DATABASE_USER", "value": "bn_wordpress" } ], "image": "bitnami/wordpress:latest", "imagePullPolicy": "", "name": "wordpress", "ports": [ { "containerPort": 80 }, { "containerPort": 443 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/bitnami/wordpress", "name": "servicenamechange-wordpress-data" }, { "mountPath": "/bitnami/apache", "name": "servicenamechange-apache-data" }, { "mountPath": "/bitnami/php", "name": "servicenamechange-php-data" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "servicenamechange-wordpress-data", "persistentVolumeClaim": { "claimName": "servicenamechange-wordpress-data" } }, { "name": "servicenamechange-apache-data", "persistentVolumeClaim": { "claimName": "servicenamechange-apache-data" } }, { "name": "servicenamechange-php-data", "persistentVolumeClaim": { "claimName": "servicenamechange-php-data" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-wordpress-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-wordpress-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-apache-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-apache-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-php-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-php-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/service-name-change/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "mariadb" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 }, { "name": "443", "port": 443, "targetPort": 443 } ], "selector": { "io.kompose.service": "wordpress" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "mariadb" ], "from": { "kind": "ImageStreamTag", "name": "mariadb:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "mariadb" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "volumes": [ { "name": "servicenamechange-mariadb-data", "persistentVolumeClaim": { "claimName": "servicenamechange-mariadb-data" } } ], "containers": [ { "name": "mariadb", "image": " ", "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_DATABASE", "value": "bitnami_wordpress" }, { "name": "MARIADB_USER", "value": "bn_wordpress" } ], "resources": {}, "volumeMounts": [ { "name": "servicenamechange-mariadb-data", "mountPath": "/bitnami/mariadb" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "mariadb", "creationTimestamp": null, "labels": { "io.kompose.service": "mariadb" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "bitnami/mariadb:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-mariadb-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-mariadb-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "wordpress" ], "from": { "kind": "ImageStreamTag", "name": "wordpress:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "wordpress" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "volumes": [ { "name": "servicenamechange-wordpress-data", "persistentVolumeClaim": { "claimName": "servicenamechange-wordpress-data" } }, { "name": "servicenamechange-apache-data", "persistentVolumeClaim": { "claimName": "servicenamechange-apache-data" } }, { "name": "servicenamechange-php-data", "persistentVolumeClaim": { "claimName": "servicenamechange-php-data" } } ], "containers": [ { "name": "wordpress", "image": " ", "ports": [ { "containerPort": 80 }, { "containerPort": 443 } ], "env": [ { "name": "ALLOW_EMPTY_PASSWORD", "value": "yes" }, { "name": "MARIADB_HOST", "value": "mariadb" }, { "name": "MARIADB_PORT", "value": "3306" }, { "name": "WORDPRESS_DATABASE_NAME", "value": "bitnami_wordpress" }, { "name": "WORDPRESS_DATABASE_USER", "value": "bn_wordpress" } ], "resources": {}, "volumeMounts": [ { "name": "servicenamechange-wordpress-data", "mountPath": "/bitnami/wordpress" }, { "name": "servicenamechange-apache-data", "mountPath": "/bitnami/apache" }, { "name": "servicenamechange-php-data", "mountPath": "/bitnami/php" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "wordpress", "creationTimestamp": null, "labels": { "io.kompose.service": "wordpress" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "bitnami/wordpress:latest" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-wordpress-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-wordpress-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-apache-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-apache-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "servicenamechange-php-data", "creationTimestamp": null, "labels": { "io.kompose.service": "servicenamechange-php-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/single-file-output/compose.yaml ================================================ services: front_end: image: gcr.io/google-samples/gb-frontend:v4 ports: - "80:80" environment: - GET_HOSTS_FROM=dns labels: kompose.service.expose: lb kompose.service.expose.ingress-class-name: nginx ================================================ FILE: script/test/fixtures/single-file-output/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: front-end name: front_end spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: front-end --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: front-end name: front-end spec: replicas: 1 selector: matchLabels: io.kompose.service: front-end template: metadata: labels: io.kompose.service: front-end spec: containers: - env: - name: GET_HOSTS_FROM value: dns image: gcr.io/google-samples/gb-frontend:v4 name: front-end ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: labels: io.kompose.service: front-end name: front-end spec: ingressClassName: nginx rules: - host: lb http: paths: - backend: service: name: front-end port: number: 80 path: / pathType: Prefix ================================================ FILE: script/test/fixtures/statefulset/compose.yaml ================================================ services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress ports: - "3306:3306" wordpress: depends_on: - db image: wordpress:latest volumes: - wordpress_data:/var/www/html ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: db_data: {} wordpress_data: {} ================================================ FILE: script/test/fixtures/statefulset/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: db name: db spec: clusterIP: None ports: - name: "3306" port: 3306 targetPort: 3306 selector: io.kompose.service: db type: ClusterIP --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: wordpress name: wordpress spec: clusterIP: None ports: - name: "8000" port: 8000 targetPort: 80 selector: io.kompose.service: wordpress type: ClusterIP --- apiVersion: apps/v1 kind: StatefulSet metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db serviceName: db template: metadata: labels: io.kompose.service: db spec: containers: - env: - name: MYSQL_DATABASE value: wordpress - name: MYSQL_PASSWORD value: wordpress - name: MYSQL_ROOT_PASSWORD value: somewordpress - name: MYSQL_USER value: wordpress image: mysql:5.7 name: db ports: - containerPort: 3306 protocol: TCP volumeMounts: - mountPath: /var/lib/mysql name: db-data restartPolicy: Always volumeClaimTemplates: - metadata: labels: io.kompose.service: db-data name: db-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi --- apiVersion: apps/v1 kind: StatefulSet metadata: labels: io.kompose.service: wordpress name: wordpress spec: replicas: 1 selector: matchLabels: io.kompose.service: wordpress serviceName: wordpress template: metadata: labels: io.kompose.service: wordpress spec: containers: - env: - name: WORDPRESS_DB_HOST value: db:3306 - name: WORDPRESS_DB_NAME value: wordpress - name: WORDPRESS_DB_PASSWORD value: wordpress - name: WORDPRESS_DB_USER value: wordpress image: wordpress:latest name: wordpress ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: /var/www/html name: wordpress-data restartPolicy: Always volumeClaimTemplates: - metadata: labels: io.kompose.service: wordpress-data name: wordpress-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/statefulset/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: db name: db spec: ports: - name: "3306" port: 3306 targetPort: 3306 selector: io.kompose.service: db --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: wordpress name: wordpress spec: ports: - name: "8000" port: 8000 targetPort: 80 selector: io.kompose.service: wordpress --- apiVersion: apps/v1 kind: StatefulSet metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db serviceName: db template: metadata: labels: io.kompose.service: db spec: containers: - env: - name: MYSQL_DATABASE value: wordpress - name: MYSQL_PASSWORD value: wordpress - name: MYSQL_ROOT_PASSWORD value: somewordpress - name: MYSQL_USER value: wordpress image: mysql:5.7 name: db ports: - containerPort: 3306 protocol: TCP volumeMounts: - mountPath: /var/lib/mysql name: db-data restartPolicy: Always volumeClaimTemplates: - metadata: labels: io.kompose.service: db-data name: db-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - env: - name: MYSQL_DATABASE value: wordpress - name: MYSQL_PASSWORD value: wordpress - name: MYSQL_ROOT_PASSWORD value: somewordpress - name: MYSQL_USER value: wordpress image: ' ' name: db ports: - containerPort: 3306 protocol: TCP volumeMounts: - mountPath: /var/lib/mysql name: db-data restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - db from: kind: ImageStreamTag name: db:5.7 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: db name: db spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: mysql:5.7 name: "5.7" referencePolicy: type: "" --- apiVersion: apps/v1 kind: StatefulSet metadata: labels: io.kompose.service: wordpress name: wordpress spec: replicas: 1 selector: matchLabels: io.kompose.service: wordpress serviceName: wordpress template: metadata: labels: io.kompose.service: wordpress spec: containers: - env: - name: WORDPRESS_DB_HOST value: db:3306 - name: WORDPRESS_DB_NAME value: wordpress - name: WORDPRESS_DB_PASSWORD value: wordpress - name: WORDPRESS_DB_USER value: wordpress image: wordpress:latest name: wordpress ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: /var/www/html name: wordpress-data restartPolicy: Always volumeClaimTemplates: - metadata: labels: io.kompose.service: wordpress-data name: wordpress-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: wordpress name: wordpress spec: replicas: 1 selector: io.kompose.service: wordpress strategy: type: Recreate template: metadata: labels: io.kompose.service: wordpress spec: containers: - env: - name: WORDPRESS_DB_HOST value: db:3306 - name: WORDPRESS_DB_NAME value: wordpress - name: WORDPRESS_DB_PASSWORD value: wordpress - name: WORDPRESS_DB_USER value: wordpress image: ' ' name: wordpress ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: /var/www/html name: wordpress-data restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - wordpress from: kind: ImageStreamTag name: wordpress:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: wordpress name: wordpress spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: wordpress:latest name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/stdin/docker-compose.yaml ================================================ services: backend: image: helloworld ================================================ FILE: script/test/fixtures/stdin/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j -f -", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "backend" }, "name": "backend" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "backend" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j -f -", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "backend" } }, "spec": { "containers": [ { "image": "helloworld", "imagePullPolicy": "", "name": "backend", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/stdin/output.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "containers": [ { "name": "redis", "image": "redis:3.0", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } }, "strategy": {} }, "status": {} } ] } ================================================ FILE: script/test/fixtures/stdin-true/docker-compose.yml ================================================ services: client: image: registry.centos.org/centos/centos:7 ports: - "1337" stdin_open: true ================================================ FILE: script/test/fixtures/stdin-true/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "1337", "port": 1337, "targetPort": 1337 } ], "selector": { "io.kompose.service": "client" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "name": "client" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "client" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "containers": [ { "image": "registry.centos.org/centos/centos:7", "imagePullPolicy": "", "name": "client", "ports": [ { "containerPort": 1337 } ], "resources": {}, "stdin": true } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/stdin-true/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "1337", "port": 1337, "targetPort": 1337 } ], "selector": { "io.kompose.service": "client" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "client" ], "from": { "kind": "ImageStreamTag", "name": "client:7" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "client" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "containers": [ { "name": "client", "image": " ", "ports": [ { "containerPort": 1337 } ], "resources": {}, "stdin": true } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "tags": [ { "name": "7", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.centos.org/centos/centos:7" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/storage-class-name/compose.yaml ================================================ services: nginx: image: nginx ports: - "80:80" labels: kompose.volume.storage-class-name: custom-storage-class-name volumes: - /etc/nginx/nginx.conf ================================================ FILE: script/test/fixtures/storage-class-name/output-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.storage-class-name": "custom-storage-class-name" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.storage-class-name": "custom-storage-class-name" } }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx" } }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.storage-class-name": "custom-storage-class-name" } }, "spec": { "volumes": [ { "name": "nginx-claim0", "persistentVolumeClaim": { "claimName": "nginx-claim0" } } ], "containers": [ { "name": "nginx", "image": "nginx", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "name": "nginx-claim0", "mountPath": "/etc/nginx/nginx.conf" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "nginx-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } }, "storageClassName": "custom-storage-class-name" }, "status": {} } ] } ================================================ FILE: script/test/fixtures/storage-class-name/output-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.volume.storage-class-name": "custom-storage-class-name" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.volume.storage-class-name": "custom-storage-class-name" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "nginx" ], "from": { "kind": "ImageStreamTag", "name": "nginx:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "nginx" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "volumes": [ { "name": "nginx-claim0", "persistentVolumeClaim": { "claimName": "nginx-claim0" } } ], "containers": [ { "name": "nginx", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "name": "nginx-claim0", "mountPath": "/etc/nginx/nginx.conf" } ] } ], "restartPolicy": "Always" } } }, "status": { "latestVersion": 0, "observedGeneration": 0, "replicas": 0, "updatedReplicas": 0, "availableReplicas": 0, "unavailableReplicas": 0 } }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "lookupPolicy": { "local": false }, "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {}, "referencePolicy": { "type": "" } } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "nginx-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } }, "storageClassName": "custom-storage-class-name" }, "status": {} } ] } ================================================ FILE: script/test/fixtures/tty-true/docker-compose.yml ================================================ services: client: image: registry.centos.org/centos/centos:7 ports: - "1337" tty: true ================================================ FILE: script/test/fixtures/tty-true/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "1337", "port": 1337, "targetPort": 1337 } ], "selector": { "io.kompose.service": "client" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "name": "client" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "client" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "containers": [ { "image": "registry.centos.org/centos/centos:7", "imagePullPolicy": "", "name": "client", "ports": [ { "containerPort": 1337 } ], "resources": {}, "tty": true } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/tty-true/output-oc.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "kompose --provider openshift -f /home/snarwade/go/src/github.com/kubernetes/kompose/script/test/fixtures/tty-true/docker-compose.yml convert --stdout -j", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "ports": [ { "name": "1337", "port": 1337, "targetPort": 1337 } ], "selector": { "io.kompose.service": "client" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "kompose --provider openshift -f /home/snarwade/go/src/github.com/kubernetes/kompose/script/test/fixtures/tty-true/docker-compose.yml convert --stdout -j", "kompose.version": "1.0.0 (HEAD)" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "client" ], "from": { "kind": "ImageStreamTag", "name": "client:7" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "client" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "containers": [ { "name": "client", "image": " ", "ports": [ { "containerPort": 1337 } ], "resources": {}, "tty": true } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "tags": [ { "name": "7", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.centos.org/centos/centos:7" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/tty-true/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "1337", "port": 1337, "targetPort": 1337 } ], "selector": { "io.kompose.service": "client" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "client" ], "from": { "kind": "ImageStreamTag", "name": "client:7" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "client" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "containers": [ { "name": "client", "image": " ", "ports": [ { "containerPort": 1337 } ], "resources": {}, "tty": true } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "client", "creationTimestamp": null, "labels": { "io.kompose.service": "client" } }, "spec": { "tags": [ { "name": "7", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.centos.org/centos/centos:7" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-hostname-multiple-ports.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" - "4000:4000" links: - redis labels: kompose.service.expose: "batman.example.com" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-hostname-tls.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: "batman.example.com" kompose.service.expose.tls-secret: "test-secret" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-hostname.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: "batman.example.com" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-multiple-hostname-tls.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: "batman.example.com,batwoman.example.com" kompose.service.expose.tls-secret: "test-secret" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-multiple-hostname.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: " batman.example.com/home ,, batwoman.example.com " redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-true-multiple-ports.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" - "4000:4000" links: - redis labels: kompose.service.expose: "True" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/compose-files/docker-compose-expose-true.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis labels: kompose.service.expose: "True" redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-hostname-multiple-ports.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "4000", "port": 4000, "targetPort": 4000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 }, { "containerPort": 4000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" } }, "spec": { "rules": [ { "host": "batman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-hostname-tls.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" } }, "spec": { "tls": [ { "hosts": [ "batman.example.com" ], "secretName": "test-secret" } ], "rules": [ { "host": "batman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-hostname.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com", "kompose.version": "%VERSION%" } }, "spec": { "rules": [ { "host": "batman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-multiple-hostname-tls.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com,batwoman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com,batwoman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com,batwoman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "batman.example.com,batwoman.example.com", "kompose.service.expose.tls-secret": "test-secret", "kompose.version": "%VERSION%" } }, "spec": { "tls": [ { "hosts": [ "batman.example.com", "batwoman.example.com" ], "secretName": "test-secret" } ], "rules": [ { "host": "batman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } }, { "host": "batwoman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-multiple-hostname.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": " batman.example.com/home ,, batwoman.example.com ", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": " batman.example.com/home ,, batwoman.example.com ", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": " batman.example.com/home ,, batwoman.example.com ", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": " batman.example.com/home ,, batwoman.example.com ", "kompose.version": "%VERSION%" } }, "spec": { "rules": [ { "host": "batman.example.com", "http": { "paths": [ { "path": "/home", "backend": { "serviceName": "web", "servicePort": 5000 } } ] } }, { "host": "batwoman.example.com", "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-true-multiple-ports.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "4000", "port": 4000, "targetPort": 4000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 }, { "containerPort": 4000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" } }, "spec": { "rules": [ { "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/kubernetes-expose-true.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "Ingress", "apiVersion": "extensions/v1beta1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.expose": "True", "kompose.version": "%VERSION%" } }, "spec": { "rules": [ { "http": { "paths": [ { "backend": { "serviceName": "web", "servicePort": 5000 } } ] } } ] }, "status": { "loadBalancer": {} } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/openshift-expose-hostname-multiple-ports.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "batman.example.com" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "4000", "port": 4000, "targetPort": 4000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "batman.example.com" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 }, { "containerPort": 4000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "Route", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "host": "batman.example.com", "to": { "kind": "Service", "name": "web", "weight": null }, "port": { "targetPort": 5000 } }, "status": { "ingress": null } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/openshift-expose-hostname.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "batman.example.com" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "batman.example.com" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "Route", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "host": "batman.example.com", "to": { "kind": "Service", "name": "web", "weight": null }, "port": { "targetPort": 5000 } }, "status": { "ingress": null } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/openshift-expose-true-multiple-ports.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "True" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "4000", "port": 4000, "targetPort": 4000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "True" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 }, { "containerPort": 4000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "Route", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "host": "", "to": { "kind": "Service", "name": "web", "weight": null }, "port": { "targetPort": 5000 } }, "status": { "ingress": null } } ] } ================================================ FILE: script/test/fixtures/unused/expose-service/provider-files/openshift-expose-true.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "True" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.service.expose": "True" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "Route", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "host": "", "to": { "kind": "Service", "name": "web", "weight": null }, "port": { "targetPort": 5000 } }, "status": { "ingress": null } } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/base.yml ================================================ services: web: image: richarvey/nginx-php-fpm environment: - ERRORS=0 - HIDE_NGINX_HEADERS=0 - REMOVE_FILES=0 - RUN_SCRIPTS=0 - PHP_ERRORS_STDERR=0 - ENABLE_XDEBUG=0 ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/compose-new-service-prob.yml ================================================ services: server: ports: - 5000:5000 new-my-service: image: nginx ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/compose-port-base.yml ================================================ services: server: image: test container_name: test_server build: context: . dockerfile: Dockerfile-dev ports: - 3000:3000 ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/compose-port-prod.yml ================================================ services: server: ports: - 5000:5000 ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/dev.yml ================================================ services: web: deploy: resources: limits: cpus: "1" # as opposed to 0.1 (default) reservations: cpus: "1" environment: - ERRORS=1 - HIDE_NGINX_HEADERS=0 - REMOVE_FILES=0 - RUN_SCRIPTS=1 - PHP_ERRORS_STDERR=1 - ENABLE_XDEBUG=1 ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/first_config.txt ================================================ First config ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/other-toplevel-base.yml ================================================ services: other-toplevel-base: image: nginx volumes: - firstvolume:/var/www/nginx configs: - source: firstconfig target: /etc/nginx.conf mode: 644 configs: firstconfig: file: ./first_config.txt ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/other-toplevel-dev.yml ================================================ services: other-toplevel-dev: image: nginx volumes: - firstvolume:/var/www/nginx configs: - source: firstconfig target: /etc/nginx.conf mode: 644 volumes: firstvolume: driver_opts: type: "nfs" configs: firstconfig: file: ./first_config.txt ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/other-toplevel-ext.yml ================================================ services: other-toplevel-second: image: nginx volumes: - secondvolume:/var/www/nginx configs: - source: secondconfig target: /etc/nginx.conf mode: 644 volumes: secondvolume: external: true configs: secondconfig: file: ./second_config.txt ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/other-toplevel-override.yml ================================================ volumes: firstvolume: external: true configs: firstconfig: file: ./second_config.txt ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-base-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "env": [ { "name": "ENABLE_XDEBUG", "value": "1" }, { "name": "ERRORS", "value": "1" }, { "name": "HIDE_NGINX_HEADERS", "value": "0" }, { "name": "PHP_ERRORS_STDERR", "value": "1" }, { "name": "REMOVE_FILES", "value": "0" }, { "name": "RUN_SCRIPTS", "value": "1" } ], "image": "richarvey/nginx-php-fpm", "imagePullPolicy": "", "name": "web", "resources": { "limits": { "cpu": "1" }, "requests": { "cpu": "1" } } } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-compose-new-service-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "server" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "new-my-service" }, "name": "new-my-service" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "new-my-service" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "new-my-service" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "new-my-service", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "name": "server" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "server" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" } }, "spec": { "containers": [ { "image": "test", "imagePullPolicy": "", "name": "test-server", "ports": [ { "containerPort": 3000 }, { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-compose-port-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "server" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "name": "server" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "server" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" } }, "spec": { "containers": [ { "image": "test", "imagePullPolicy": "", "name": "test-server", "ports": [ { "containerPort": 3000 }, { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-openshift-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "server" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "new-my-service", "creationTimestamp": null, "labels": { "io.kompose.service": "new-my-service" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "new-my-service" ], "from": { "kind": "ImageStreamTag", "name": "new-my-service:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "new-my-service" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "new-my-service" } }, "spec": { "containers": [ { "name": "new-my-service", "image": " ", "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "new-my-service", "creationTimestamp": null, "labels": { "io.kompose.service": "new-my-service" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "test_server" ], "from": { "kind": "ImageStreamTag", "name": "server:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "server" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "server" } }, "spec": { "containers": [ { "name": "test-server", "image": " ", "ports": [ { "containerPort": 3000 }, { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "test" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-other-toplevel-merge-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "firstconfig", "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-dev" } }, "data": { "first_config.txt": "First config\n" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-dev" }, "name": "other-toplevel-dev" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "other-toplevel-dev" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-dev" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "other-toplevel-dev", "resources": {}, "volumeMounts": [ { "mountPath": "/etc/nginx.conf", "name": "firstconfig", "subPath": "nginx.conf" }, { "mountPath": "/var/www/nginx", "name": "firstvolume" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "defaultMode": 644, "items": [ { "key": "first_config.txt", "path": "nginx.conf" } ], "name": "firstconfig" }, "name": "firstconfig" }, { "name": "firstvolume", "persistentVolumeClaim": { "claimName": "firstvolume" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "firstvolume", "creationTimestamp": null, "labels": { "io.kompose.service": "firstvolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "secondconfig", "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-second" } }, "data": { "second_config.txt": "Second config\n" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-second" }, "name": "other-toplevel-second" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "other-toplevel-second" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-second" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "other-toplevel-second", "resources": {}, "volumeMounts": [ { "mountPath": "/etc/nginx.conf", "name": "secondconfig", "subPath": "nginx.conf" }, { "mountPath": "/var/www/nginx", "name": "secondvolume" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "defaultMode": 644, "items": [ { "key": "second_config.txt", "path": "nginx.conf" } ], "name": "secondconfig" }, "name": "secondconfig" }, { "name": "secondvolume", "persistentVolumeClaim": { "claimName": "secondvolume" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "secondvolume", "creationTimestamp": null, "labels": { "io.kompose.service": "secondvolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-other-toplevel-override-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ConfigMap", "apiVersion": "v1", "metadata": { "name": "firstconfig", "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-base" } }, "data": { "second_config.txt": "Second config\n" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-base" }, "name": "other-toplevel-base" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "other-toplevel-base" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "other-toplevel-base" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "other-toplevel-base", "resources": {}, "volumeMounts": [ { "mountPath": "/etc/nginx.conf", "name": "firstconfig", "subPath": "nginx.conf" }, { "mountPath": "/var/www/nginx", "name": "firstvolume" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "configMap": { "defaultMode": 644, "items": [ { "key": "second_config.txt", "path": "nginx.conf" } ], "name": "firstconfig" }, "name": "firstconfig" }, { "name": "firstvolume", "persistentVolumeClaim": { "claimName": "firstvolume" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "firstvolume", "creationTimestamp": null, "labels": { "io.kompose.service": "firstvolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/output-service-merge-concat-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "server", "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "annotations": { "complexlabel": "override", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "simplelabel.first": "Foo", "simplelabel.second": "Bar" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "server" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "complexlabel": "override", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "simplelabel.first": "Foo", "simplelabel.second": "Bar" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" }, "name": "server" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "server" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "complexlabel": "override", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "simplelabel.first": "Foo", "simplelabel.second": "Bar" }, "creationTimestamp": null, "labels": { "io.kompose.service": "server" } }, "spec": { "containers": [ { "env": [ { "name": "NEW_ONE", "value": "1234" }, { "name": "SAMPLE_TO_BE_OVERRIDDEN", "value": "new" }, { "name": "SIMPLE_ONE", "value": "true" } ], "image": "test", "imagePullPolicy": "", "name": "test-server", "ports": [ { "containerPort": 3000 }, { "containerPort": 5000 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/simple/base/volume", "name": "server-claim0" }, { "mountPath": "/common/mount/point", "name": "server-claim1" }, { "mountPath": "/additional/added/volume", "name": "server-claim2" }, { "mountPath": "/base-tmpdir", "name": "server-tmpfs0" }, { "mountPath": "/additional-tmpdir", "name": "server-tmpfs1" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "server-claim0", "persistentVolumeClaim": { "claimName": "server-claim0" } }, { "name": "server-claim1", "persistentVolumeClaim": { "claimName": "server-claim1" } }, { "name": "server-claim2", "persistentVolumeClaim": { "claimName": "server-claim2" } }, { "emptyDir": { "medium": "Memory" }, "name": "server-tmpfs0" }, { "emptyDir": { "medium": "Memory" }, "name": "server-tmpfs1" } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "server-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "server-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "server-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "server-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "server-claim2", "creationTimestamp": null, "labels": { "io.kompose.service": "server-claim2" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/second_config.txt ================================================ Second config ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/service-merge-concat-base.yml ================================================ services: server: image: test container_name: test_server environment: - SIMPLE_ONE=true - SAMPLE_TO_BE_OVERRIDDEN=original expose: - "3000" dns: - 1.1.1.1 - 8.8.8.8 dns_search: first.search.domain.com external_links: - other_service_1 - common_service:alias1 labels: - "simplelabel.first=Foo" - "complexlabel=original" tmpfs: - /base-tmpdir volumes: - /simple/base/volume - /base/dir:/common/mount/point ================================================ FILE: script/test/fixtures/unused/merge-multiple-compose/service-merge-concat-override.yml ================================================ services: server: image: test container_name: test_server environment: - SAMPLE_TO_BE_OVERRIDDEN=new - NEW_ONE=1234 expose: - "5000" dns: 9.9.9.9 dns_search: - second.search.domain.com external_links: - other_service_2 - common_service:alias2 labels: simplelabel.second: "Bar" complexlabel: override tmpfs: /additional-tmpdir volumes: - /additional/added/volume - /override/dir:/common/mount/point ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-3.5.yaml ================================================ services: helloworld: image: tutum/hello-world ports: - 80:80 expose: # this may be mis-usage, only to test expose with ports. - "3000" - "80" networks: - helloworld-network networks: helloworld-network: name: helloworld-network ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-deploy.yaml ================================================ services: vote: # test placement and update_config image: dockersamples/examplevotingapp_vote:before depends_on: - redis deploy: replicas: 2 placement: constraints: [node.hostname == machine] update_config: parallelism: 2 delay: 10s order: stop-first db: # test placement image: postgres deploy: placement: constraints: - node.hostname == machine - engine.labels.operatingsystem == ubuntu 14.04 - node.labels.foo != bar foo: # test labels labels: kompose.service.type: headless deploy: mode: global replicas: 6 update_config: parallelism: 2 delay: 10s order: stop-first labels: com.example.description: "This label will appear on the web service" image: redis ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-env-subs.yaml ================================================ services: foo: image: foo/bar:latest environment: FOO: $foo ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-env.yaml ================================================ services: foo: labels: kompose.service.type: headless image: foo/bar:latest environment: FOO: foo ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-full-example.yaml ================================================ services: foo: cap_add: - ALL cap_drop: - NET_ADMIN - SYS_ADMIN cgroup_parent: m-executor-abcd # String or list command: bundle exec thin -p 3000 # command: ["bundle", "exec", "thin", "-p", "3000"] container_name: my_web-container depends_on: - db - redis deploy: mode: replicated replicas: 6 labels: [FOO=BAR] update_config: parallelism: 3 delay: 10s failure_action: continue monitor: 60s max_failure_ratio: 0.3 resources: limits: cpus: '0.001' memory: 50M reservations: cpus: '0.0001' memory: 20M restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 120s placement: constraints: [node=foo] devices: - "/dev/ttyUSB0:/dev/ttyUSB0" # String or list # dns: 8.8.8.8 dns: - 8.8.8.8 - 9.9.9.9 # String or list # dns_search: example.com dns_search: - dc1.example.com - dc2.example.com domainname: foo.com # String or list # entrypoint: /code/entrypoint.sh -p 3000 entrypoint: ["/code/entrypoint.sh", "-p", "3000"] # Items can be strings or numbers expose: - "3000" - 8000 external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql # Mapping or list # Mapping values must be strings # extra_hosts: # somehost: "162.242.195.82" # otherhost: "50.31.209.229" extra_hosts: - "somehost:162.242.195.82" - "otherhost:50.31.209.229" hostname: foo healthcheck: test: echo "hello world" interval: 10s timeout: 1s retries: 5 # Any valid image reference - repo, tag, id, sha image: redis # image: ubuntu:14.04 # image: tutum/influxdb # image: example-registry.com:4000/postgresql # image: a4bc65fd # image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d ipc: host # Mapping or list # Mapping values can be strings, numbers or null labels: com.example.description: "Accounting webapp" com.example.number: 42 com.example.empty-label: # labels: # - "com.example.description=Accounting webapp" # - "com.example.number=42" # - "com.example.empty-label" links: - db - db:database - redis logging: driver: syslog options: syslog-address: "tcp://192.168.0.42:123" mac_address: 02:42:ac:11:65:43 # network_mode: "bridge" # network_mode: "host" # network_mode: "none" # Use the network mode of an arbitrary container from another service # network_mode: "service:db" # Use the network mode of another container, specified by name or id # network_mode: "container:some-container" network_mode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b" networks: some-network: aliases: - alias1 - alias3 other-network: ipv4_address: 172.16.238.10 ipv6_address: 2001:3984:3989::10 other-other-network: pid: "host" ports: - 3000 - "3000-3005" - "8000:8000" - "9090-9091:8080-8081" - "49100:22" - "127.0.0.1:8001:8001" - "127.0.0.1:5000-5010:5000-5010" privileged: true read_only: true restart: always security_opt: - label=level:s0:c100,c200 - label=type:svirt_apache_t stdin_open: true stop_grace_period: 20s stop_signal: SIGUSR1 # String or list # tmpfs: /run tmpfs: - /run - /tmp tty: true ulimits: # Single number or mapping with soft + hard limits nproc: 65535 nofile: soft: 20000 hard: 40000 user: someone volumes: # Just specify a path and let the Engine create a volume - /var/lib/mysql # Specify an absolute path mapping - /opt/data:/var/lib/mysql # Path on the host, relative to the Compose file - .:/code - ./static:/var/www/html # User-relative path - ~/configs:/etc/configs/:ro # Named volume - datavolume:/var/lib/mysql working_dir: /code networks: # Entries can be null, which specifies simply that a network # called "{project name}_some-network" should be created and # use the default driver some-network: other-network: driver: overlay driver_opts: # Values can be strings or numbers foo: "bar" baz: 1 ipam: driver: overlay # driver_opts: # # Values can be strings or numbers # com.docker.network.enable_ipv6: "true" # com.docker.network.numeric_value: 1 config: - subnet: 172.16.238.0/24 # gateway: 172.16.238.1 - subnet: 2001:3984:3989::/64 # gateway: 2001:3984:3989::1 external-network: # Specifies that a pre-existing network called "external-network" # can be referred to within this file as "external-network" external: true other-external-network: # Specifies that a pre-existing network called "my-cool-network" # can be referred to within this file as "other-external-network" external: name: my-cool-network volumes: # Entries can be null, which specifies simply that a volume # called "{project name}_some-volume" should be created and # use the default driver some-volume: other-volume: driver: flocker driver_opts: # Values can be strings or numbers foo: "bar" baz: 1 external-volume: # Specifies that a pre-existing volume called "external-volume" # can be referred to within this file as "external-volume" external: true other-external-volume: # Specifies that a pre-existing volume called "my-cool-volume" # can be referred to within this file as "other-external-volume" external: name: my-cool-volume ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-memcpu-partial.yaml ================================================ services: foo: labels: kompose.service.type: headless deploy: resources: limits: memory: 50M reservations: cpus: '0.001' image: redis ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-memcpu.yaml ================================================ services: foo: labels: kompose.service.type: headless deploy: resources: limits: cpus: '0.01' memory: 50M reservations: cpus: '0.001' memory: 20M image: redis ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-unset-env.yaml ================================================ services: foo: labels: kompose.service.type: headless image: foo/bar:latest environment: - BAR - V3_HOST_ENV_TEST_SET_TO_BAR - FOO=foo ================================================ FILE: script/test/fixtures/unused/v3/docker-compose-volumes.yaml ================================================ services: foobar: labels: kompose.service.type: headless image: foo/bar:latest volumes: - /tmp/foo/bar ================================================ FILE: script/test/fixtures/unused/v3/docker-compose.yaml ================================================ services: redis-master: image: registry.k8s.io/redis:e2e ports: - "6379" redis-replica: image: registry.k8s.io/redis-slave:v2 ports: - "6379" environment: - GET_HOSTS_FROM=dns frontend: image: gcr.io/google-samples/gb-frontend:v4 ports: - "80:80" environment: - GET_HOSTS_FROM=dns labels: kompose.service.type: LoadBalancer ================================================ FILE: script/test/fixtures/unused/v3/example1.env ================================================ # passed through FOO=1 # overridden in example2.env BAR=1 # overridden in full-example.yml BAZ=1 ================================================ FILE: script/test/fixtures/unused/v3/example2.env ================================================ BAR=2 ================================================ FILE: script/test/fixtures/unused/v3/output-deploy-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "db" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "image": "postgres", "imagePullPolicy": "", "name": "db", "resources": {} } ], "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname", "operator": "In", "values": ["machine"] }, { "key": "kubernetes.io/oss", "operator": "In", "values": ["ubuntu 14.04"] }, { "key": "foo", "operator": "NotIn", "values": ["bar"] } ] } ] } } }, "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "com.example.description": "This label will appear on the web service", "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "foo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "name": "vote" }, "spec": { "replicas": 2, "selector": { "matchLabels": { "io.kompose.service": "vote" } }, "strategy": { "rollingUpdate": { "maxSurge": 0, "maxUnavailable": 2 }, "type": "RollingUpdate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "containers": [ { "image": "dockersamples/examplevotingapp_vote:before", "imagePullPolicy": "", "name": "vote", "resources": {} } ], "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname", "operator": "In", "values": ["machine"] } ] } ] } } }, "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-deploy-os.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "db" ], "from": { "kind": "ImageStreamTag", "name": "db:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "db" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "name": "db", "image": " ", "resources": {} } ], "restartPolicy": "Always", "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname", "operator": "In", "values": ["machine"] }, { "key": "kubernetes.io/os", "operator": "In", "values": ["ubuntu 14.04"] }, { "key": "foo", "operator": "NotIn", "values": ["bar"] } ] } ] } } } } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "postgres" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "apiVersion": "apps/v1", "kind": "DaemonSet", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "com.example.description": "This label will appear on the web service", "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "foo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": { "currentNumberScheduled": 0, "desiredNumberScheduled": 0, "numberMisscheduled": 0 } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Rolling", "rollingParams": { "updatePeriodSeconds": 10, "maxUnavailable": 2, "maxSurge": 0 }, "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "foo" ], "from": { "kind": "ImageStreamTag", "name": "foo:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "foo" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "name": "foo", "image": " ", "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "redis" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Rolling", "rollingParams": { "updatePeriodSeconds": 10, "maxUnavailable": 2, "maxSurge": 0 }, "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "vote" ], "from": { "kind": "ImageStreamTag", "name": "vote:before" } } } ], "replicas": 2, "test": false, "selector": { "io.kompose.service": "vote" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "containers": [ { "name": "vote", "image": " ", "resources": {} } ], "restartPolicy": "Always", "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname", "operator": "In", "values": ["machine"] } ] } ] } } } } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "vote", "creationTimestamp": null, "labels": { "io.kompose.service": "vote" } }, "spec": { "tags": [ { "name": "before", "annotations": null, "from": { "kind": "DockerImage", "name": "dockersamples/examplevotingapp_vote:before" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-env-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "env": [ { "name": "FOO", "value": "foo" } ], "image": "foo/bar:latest", "imagePullPolicy": "", "name": "foo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-env-subs.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "env": [ { "name": "FOO" } ], "image": "foo/bar:latest", "imagePullPolicy": "", "name": "foo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-k8s-3.5.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "helloworld", "creationTimestamp": null, "labels": { "io.kompose.service": "helloworld" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 }, { "name": "3000", "port": 3000, "targetPort": 3000 } ], "selector": { "io.kompose.service": "helloworld" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "helloworld" }, "name": "helloworld" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "helloworld" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.network/helloworld-network": "true", "io.kompose.service": "helloworld" } }, "spec": { "containers": [ { "image": "tutum/hello-world", "imagePullPolicy": "", "name": "helloworld", "ports": [ { "containerPort": 80 }, { "containerPort": 3000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "helloworld-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/helloworld-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/helloworld-network": "true" } } } ] } ] } } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-k8s-full-example-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.network/other-network": "true", "io.kompose.network/other-other-network": "true", "io.kompose.network/some-network": "true", "io.kompose.service": "foo" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "foo-claim2", "persistentVolumeClaim": { "claimName": "foo-claim2" } }, { "name": "foo-claim3", "persistentVolumeClaim": { "claimName": "foo-claim3" } }, { "name": "foo-claim4", "persistentVolumeClaim": { "claimName": "foo-claim4", "readOnly": true } }, { "name": "datavolume", "persistentVolumeClaim": { "claimName": "datavolume" } }, { "name": "foo-tmpfs0", "emptyDir": { "medium": "Memory" } }, { "name": "foo-tmpfs1", "emptyDir": { "medium": "Memory" } } ], "containers": [ { "name": "my-web-container", "image": "redis", "command": [ "/code/entrypoint.sh", "-p", "3000" ], "args": [ "bundle", "exec", "thin", "-p", "3000" ], "workingDir": "/code", "ports": [ { "containerPort": 3000 }, { "containerPort": 3000 }, { "containerPort": 3001 }, { "containerPort": 3002 }, { "containerPort": 3003 }, { "containerPort": 3004 }, { "containerPort": 3005 }, { "containerPort": 8000 }, { "containerPort": 8080 }, { "containerPort": 8081 }, { "containerPort": 22 }, { "containerPort": 8001 }, { "containerPort": 5000 }, { "containerPort": 5001 }, { "containerPort": 5002 }, { "containerPort": 5003 }, { "containerPort": 5004 }, { "containerPort": 5005 }, { "containerPort": 5006 }, { "containerPort": 5007 }, { "containerPort": 5008 }, { "containerPort": 5009 }, { "containerPort": 5010 } ], "resources": { "limits": { "memory": "52428800" } }, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim1", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim2", "mountPath": "/code" }, { "name": "foo-claim3", "mountPath": "/var/www/html" }, { "name": "foo-claim4", "readOnly": true, "mountPath": "/etc/configs/" }, { "name": "datavolume", "mountPath": "/var/lib/mysql" }, { "name": "foo-tmpfs0", "mountPath": "/run" }, { "name": "foo-tmpfs1", "mountPath": "/tmp" } ], "securityContext": { "capabilities": { "add": [ "ALL" ], "drop": [ "NET_ADMIN", "SYS_ADMIN" ] }, "privileged": true }, "stdin": true, "tty": true } ], "restartPolicy": "OnFailure" }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim2", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim2" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim3", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim3" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim4", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim4" } }, "spec": { "accessModes": [ "ReadOnlyMany" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "datavolume", "creationTimestamp": null, "labels": { "io.kompose.service": "datavolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "other-other-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/other-other-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/other-other-network": "true" } } } ] } ] } }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "some-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/some-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/some-network": "true" } } } ] } ] } }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "other-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/other-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/other-network": "true" } } } ] } ] } } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-k8s-full-example.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "com.example.description": "Accounting webapp", "com.example.empty-label": "", "com.example.number": "42", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "3000-tcp", "port": 3000, "targetPort": 3000 }, { "name": "3001", "port": 3001, "targetPort": 3001 }, { "name": "3002", "port": 3002, "targetPort": 3002 }, { "name": "3003", "port": 3003, "targetPort": 3003 }, { "name": "3004", "port": 3004, "targetPort": 3004 }, { "name": "3005", "port": 3005, "targetPort": 3005 }, { "name": "8000", "port": 8000, "targetPort": 8000 }, { "name": "9090", "port": 9090, "targetPort": 8080 }, { "name": "9091", "port": 9091, "targetPort": 8081 }, { "name": "49100", "port": 49100, "targetPort": 22 }, { "name": "8001", "port": 8001, "targetPort": 8001 }, { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "5001", "port": 5001, "targetPort": 5001 }, { "name": "5002", "port": 5002, "targetPort": 5002 }, { "name": "5003", "port": 5003, "targetPort": 5003 }, { "name": "5004", "port": 5004, "targetPort": 5004 }, { "name": "5005", "port": 5005, "targetPort": 5005 }, { "name": "5006", "port": 5006, "targetPort": 5006 }, { "name": "5007", "port": 5007, "targetPort": 5007 }, { "name": "5008", "port": 5008, "targetPort": 5008 }, { "name": "5009", "port": 5009, "targetPort": 5009 }, { "name": "5010", "port": 5010, "targetPort": 5010 } ], "selector": { "io.kompose.service": "foo" } }, "status": { "loadBalancer": {} } }, { "kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.network/other-network": "true", "io.kompose.network/other-other-network": "true", "io.kompose.network/some-network": "true", "io.kompose.service": "foo" }, "annotations": { "com.example.description": "Accounting webapp", "com.example.empty-label": "", "com.example.number": "42", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "foo-claim2", "persistentVolumeClaim": { "claimName": "foo-claim2" } }, { "name": "foo-claim3", "persistentVolumeClaim": { "claimName": "foo-claim3" } }, { "name": "foo-claim4", "persistentVolumeClaim": { "claimName": "foo-claim4", "readOnly": true } }, { "name": "datavolume", "persistentVolumeClaim": { "claimName": "datavolume" } }, { "name": "foo-tmpfs0", "emptyDir": { "medium": "Memory" } }, { "name": "foo-tmpfs1", "emptyDir": { "medium": "Memory" } } ], "containers": [ { "name": "my-web-container", "image": "redis", "command": [ "/code/entrypoint.sh", "-p", "3000" ], "args": [ "bundle", "exec", "thin", "-p", "3000" ], "workingDir": "/code", "ports": [ { "containerPort": 3000 }, { "containerPort": 3001 }, { "containerPort": 3002 }, { "containerPort": 3003 }, { "containerPort": 3004 }, { "containerPort": 3005 }, { "containerPort": 8000 }, { "containerPort": 8080 }, { "containerPort": 8081 }, { "containerPort": 22 }, { "containerPort": 8001 }, { "containerPort": 5000 }, { "containerPort": 5001 }, { "containerPort": 5002 }, { "containerPort": 5003 }, { "containerPort": 5004 }, { "containerPort": 5005 }, { "containerPort": 5006 }, { "containerPort": 5007 }, { "containerPort": 5008 }, { "containerPort": 5009 }, { "containerPort": 5010 } ], "resources": { "limits": { "cpu": "1m", "memory": "52428800" }, "requests": { "memory": "20971520" } }, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim1", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim2", "mountPath": "/code" }, { "name": "foo-claim3", "mountPath": "/var/www/html" }, { "name": "foo-claim4", "readOnly": true, "mountPath": "/etc/configs/" }, { "name": "datavolume", "mountPath": "/var/lib/mysql" }, { "name": "foo-tmpfs0", "mountPath": "/run" }, { "name": "foo-tmpfs1", "mountPath": "/tmp" } ], "livenessProbe": { "exec": { "command": [ "echo \"hello world\"" ] }, "timeoutSeconds": 1, "periodSeconds": 10, "failureThreshold": 5 }, "securityContext": { "capabilities": { "add": [ "ALL" ], "drop": [ "NET_ADMIN", "SYS_ADMIN" ] }, "privileged": true }, "stdin": true, "tty": true } ], "restartPolicy": "OnFailure", "terminationGracePeriodSeconds": 20, "hostname": "foo", "subdomain": "foo.com" }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim2", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim2" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim3", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim3" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim4", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim4" } }, "spec": { "accessModes": [ "ReadOnlyMany" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "datavolume", "creationTimestamp": null, "labels": { "io.kompose.service": "datavolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "some-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/some-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/some-network": "true" } } } ] } ] } }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "other-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/other-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/other-network": "true" } } } ] } ] } }, { "kind": "NetworkPolicy", "apiVersion": "extensions/v1beta1", "metadata": { "name": "other-other-network", "creationTimestamp": null }, "spec": { "podSelector": { "matchLabels": { "io.kompose.network/other-other-network": "true" } }, "ingress": [ { "from": [ { "podSelector": { "matchLabels": { "io.kompose.network/other-other-network": "true" } } } ] } ] } } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "name": "frontend" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "frontend" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "gcr.io/google-samples/gb-frontend:v4", "imagePullPolicy": "", "name": "frontend", "ports": [ { "containerPort": 80 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-master" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "name": "redis-replica" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-replica" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "image": "registry.k8s.io/redis-slave:v2", "imagePullPolicy": "", "name": "redis-replica", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-memcpu-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "foo", "resources": { "limits": { "cpu": "10m", "memory": "52428800" }, "requests": { "cpu": "1m", "memory": "20971520" } } } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-memcpu-partial-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "image": "redis", "imagePullPolicy": "", "name": "foo", "resources": { "limits": { "memory": "52428800" }, "requests": { "cpu": "1m" } } } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-os-full-example.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "com.example.description": "Accounting webapp", "com.example.empty-label": "", "com.example.number": "42", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3000", "port": 3000, "targetPort": 3000 }, { "name": "3000-tcp", "port": 3000, "targetPort": 3000 }, { "name": "3001", "port": 3001, "targetPort": 3001 }, { "name": "3002", "port": 3002, "targetPort": 3002 }, { "name": "3003", "port": 3003, "targetPort": 3003 }, { "name": "3004", "port": 3004, "targetPort": 3004 }, { "name": "3005", "port": 3005, "targetPort": 3005 }, { "name": "8000", "port": 8000, "targetPort": 8000 }, { "name": "9090", "port": 9090, "targetPort": 8080 }, { "name": "9091", "port": 9091, "targetPort": 8081 }, { "name": "49100", "port": 49100, "targetPort": 22 }, { "name": "8001", "port": 8001, "targetPort": 8001 }, { "name": "5000", "port": 5000, "targetPort": 5000 }, { "name": "5001", "port": 5001, "targetPort": 5001 }, { "name": "5002", "port": 5002, "targetPort": 5002 }, { "name": "5003", "port": 5003, "targetPort": 5003 }, { "name": "5004", "port": 5004, "targetPort": 5004 }, { "name": "5005", "port": 5005, "targetPort": 5005 }, { "name": "5006", "port": 5006, "targetPort": 5006 }, { "name": "5007", "port": 5007, "targetPort": 5007 }, { "name": "5008", "port": 5008, "targetPort": 5008 }, { "name": "5009", "port": 5009, "targetPort": 5009 }, { "name": "5010", "port": 5010, "targetPort": 5010 } ], "selector": { "io.kompose.service": "foo" } }, "status": { "loadBalancer": {} } }, { "kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.network/other-network": "true", "io.kompose.network/other-other-network": "true", "io.kompose.network/some-network": "true", "io.kompose.service": "foo" }, "annotations": { "com.example.description": "Accounting webapp", "com.example.empty-label": "", "com.example.number": "42", "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "foo-claim2", "persistentVolumeClaim": { "claimName": "foo-claim2" } }, { "name": "foo-claim3", "persistentVolumeClaim": { "claimName": "foo-claim3" } }, { "name": "foo-claim4", "persistentVolumeClaim": { "claimName": "foo-claim4", "readOnly": true } }, { "name": "datavolume", "persistentVolumeClaim": { "claimName": "datavolume" } }, { "name": "foo-tmpfs0", "emptyDir": { "medium": "Memory" } }, { "name": "foo-tmpfs1", "emptyDir": { "medium": "Memory" } } ], "containers": [ { "name": "my-web-container", "image": "redis", "command": [ "/code/entrypoint.sh", "-p", "3000" ], "args": [ "bundle", "exec", "thin", "-p", "3000" ], "workingDir": "/code", "ports": [ { "containerPort": 3000 }, { "containerPort": 3001 }, { "containerPort": 3002 }, { "containerPort": 3003 }, { "containerPort": 3004 }, { "containerPort": 3005 }, { "containerPort": 8000 }, { "containerPort": 8080 }, { "containerPort": 8081 }, { "containerPort": 22 }, { "containerPort": 8001 }, { "containerPort": 5000 }, { "containerPort": 5001 }, { "containerPort": 5002 }, { "containerPort": 5003 }, { "containerPort": 5004 }, { "containerPort": 5005 }, { "containerPort": 5006 }, { "containerPort": 5007 }, { "containerPort": 5008 }, { "containerPort": 5009 }, { "containerPort": 5010 } ], "resources": { "limits": { "cpu": "1m", "memory": "52428800" }, "requests": { "memory": "20971520" } }, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim1", "mountPath": "/var/lib/mysql" }, { "name": "foo-claim2", "mountPath": "/code" }, { "name": "foo-claim3", "mountPath": "/var/www/html" }, { "name": "foo-claim4", "readOnly": true, "mountPath": "/etc/configs/" }, { "name": "datavolume", "mountPath": "/var/lib/mysql" }, { "name": "foo-tmpfs0", "mountPath": "/run" }, { "name": "foo-tmpfs1", "mountPath": "/tmp" } ], "livenessProbe": { "exec": { "command": [ "echo \"hello world\"" ] }, "timeoutSeconds": 1, "periodSeconds": 10, "failureThreshold": 5 }, "securityContext": { "capabilities": { "add": [ "ALL" ], "drop": [ "NET_ADMIN", "SYS_ADMIN" ] }, "privileged": true }, "stdin": true, "tty": true } ], "restartPolicy": "OnFailure", "terminationGracePeriodSeconds": 20, "hostname": "foo", "subdomain": "foo.com" }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim2", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim2" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim3", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim3" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim4", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim4" } }, "spec": { "accessModes": [ "ReadOnlyMany" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "datavolume", "creationTimestamp": null, "labels": { "io.kompose.service": "datavolume" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "frontend" }, "type": "LoadBalancer" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-replica" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "LoadBalancer", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "frontend" ], "from": { "kind": "ImageStreamTag", "name": "frontend:v4" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "frontend" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "containers": [ { "name": "frontend", "image": " ", "ports": [ { "containerPort": 80 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "frontend", "creationTimestamp": null, "labels": { "io.kompose.service": "frontend" } }, "spec": { "tags": [ { "name": "v4", "annotations": null, "from": { "kind": "DockerImage", "name": "gcr.io/google-samples/gb-frontend:v4" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-master" ], "from": { "kind": "ImageStreamTag", "name": "redis-master:e2e" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-master" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "name": "redis-master", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "tags": [ { "name": "e2e", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis:e2e" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-replica" ], "from": { "kind": "ImageStreamTag", "name": "redis-replica:v1" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-replica" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "containers": [ { "name": "redis-replica", "image": " ", "ports": [ { "containerPort": 6379 } ], "env": [ { "name": "GET_HOSTS_FROM", "value": "dns" } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-replica", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-replica" } }, "spec": { "tags": [ { "name": "v1", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis-slave:v2" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-unset-env-k8s.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" }, "name": "foo" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foo" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "containers": [ { "env": [ { "name": "FOO", "value": "foo" }, { "name": "V3_HOST_ENV_TEST_SET_TO_BAR", "value": "BAR" } ], "image": "foo/bar:latest", "imagePullPolicy": "", "name": "foo", "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/unused/v3/output-volumes-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foobar", "creationTimestamp": null, "labels": { "io.kompose.service": "foobar" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foobar" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foobar" }, "name": "foobar" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "foobar" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.service.type": "headless", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "foobar" } }, "spec": { "containers": [ { "image": "foo/bar:latest", "imagePullPolicy": "", "name": "foobar", "resources": {}, "volumeMounts": [ { "mountPath": "/tmp/foo/bar", "name": "foobar-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "foobar-claim0", "persistentVolumeClaim": { "claimName": "foobar-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foobar-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foobar-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/v2/compose.yaml ================================================ services: foo: # create a pod image: "foobar" restart: "no" environment: GITHUB: surajssd ports: - "6379/tcp" - "6379/udp" - "3000" - "3000-3005" - "8000:8000" - "9090-9091:8080-8081" - "49100:22" - "127.0.0.1:8001:8001" - "127.0.0.1:5000-5010:5000-5010" mem_limit: 10000 group_add: - "1234" redis: image: redis:3.0 labels: kompose.service.type: loadbalancer ports: - "6379/tcp" - "1234:1235/udp" mem_limit: 10000Mb ================================================ FILE: script/test/fixtures/v2/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: foo name: foo spec: ports: - name: "6379" port: 6379 targetPort: 6379 - name: 6379-udp port: 6379 protocol: UDP targetPort: 6379 - name: "3000" port: 3000 targetPort: 3000 - name: "3001" port: 3001 targetPort: 3001 - name: "3002" port: 3002 targetPort: 3002 - name: "3003" port: 3003 targetPort: 3003 - name: "3004" port: 3004 targetPort: 3004 - name: "3005" port: 3005 targetPort: 3005 - name: "8000" port: 8000 targetPort: 8000 - name: "9090" port: 9090 targetPort: 8080 - name: "9091" port: 9091 targetPort: 8081 - name: "49100" port: 49100 targetPort: 22 - name: "8001" port: 8001 targetPort: 8001 - name: "5000" port: 5000 targetPort: 5000 - name: "5001" port: 5001 targetPort: 5001 - name: "5002" port: 5002 targetPort: 5002 - name: "5003" port: 5003 targetPort: 5003 - name: "5004" port: 5004 targetPort: 5004 - name: "5005" port: 5005 targetPort: 5005 - name: "5006" port: 5006 targetPort: 5006 - name: "5007" port: 5007 targetPort: 5007 - name: "5008" port: 5008 targetPort: 5008 - name: "5009" port: 5009 targetPort: 5009 - name: "5010" port: 5010 targetPort: 5010 selector: io.kompose.service: foo --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis-tcp name: redis-tcp spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis type: LoadBalancer --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis-udp name: redis-udp spec: ports: - name: "1234" port: 1234 protocol: UDP targetPort: 1235 selector: io.kompose.service: redis type: LoadBalancer --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: foo name: foo spec: containers: - env: - name: GITHUB value: surajssd image: foobar name: foo ports: - containerPort: 6379 protocol: TCP - containerPort: 6379 protocol: UDP - containerPort: 3000 protocol: TCP - containerPort: 3001 protocol: TCP - containerPort: 3002 protocol: TCP - containerPort: 3003 protocol: TCP - containerPort: 3004 protocol: TCP - containerPort: 3005 protocol: TCP - containerPort: 8000 protocol: TCP - containerPort: 8080 protocol: TCP - containerPort: 8081 protocol: TCP - containerPort: 22 protocol: TCP - containerPort: 8001 protocol: TCP - containerPort: 5000 protocol: TCP - containerPort: 5001 protocol: TCP - containerPort: 5002 protocol: TCP - containerPort: 5003 protocol: TCP - containerPort: 5004 protocol: TCP - containerPort: 5005 protocol: TCP - containerPort: 5006 protocol: TCP - containerPort: 5007 protocol: TCP - containerPort: 5008 protocol: TCP - containerPort: 5009 protocol: TCP - containerPort: 5010 protocol: TCP resources: limits: memory: "10e3" restartPolicy: Never securityContext: supplementalGroups: - 1234 --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis:3.0 name: redis ports: - containerPort: 6379 protocol: TCP - containerPort: 1235 protocol: UDP resources: limits: memory: "10485760e3" restartPolicy: Always ================================================ FILE: script/test/fixtures/v2/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: foo name: foo spec: ports: - name: "6379" port: 6379 targetPort: 6379 - name: 6379-udp port: 6379 protocol: UDP targetPort: 6379 - name: "3000" port: 3000 targetPort: 3000 - name: "3001" port: 3001 targetPort: 3001 - name: "3002" port: 3002 targetPort: 3002 - name: "3003" port: 3003 targetPort: 3003 - name: "3004" port: 3004 targetPort: 3004 - name: "3005" port: 3005 targetPort: 3005 - name: "8000" port: 8000 targetPort: 8000 - name: "9090" port: 9090 targetPort: 8080 - name: "9091" port: 9091 targetPort: 8081 - name: "49100" port: 49100 targetPort: 22 - name: "8001" port: 8001 targetPort: 8001 - name: "5000" port: 5000 targetPort: 5000 - name: "5001" port: 5001 targetPort: 5001 - name: "5002" port: 5002 targetPort: 5002 - name: "5003" port: 5003 targetPort: 5003 - name: "5004" port: 5004 targetPort: 5004 - name: "5005" port: 5005 targetPort: 5005 - name: "5006" port: 5006 targetPort: 5006 - name: "5007" port: 5007 targetPort: 5007 - name: "5008" port: 5008 targetPort: 5008 - name: "5009" port: 5009 targetPort: 5009 - name: "5010" port: 5010 targetPort: 5010 selector: io.kompose.service: foo --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis-tcp name: redis-tcp spec: ports: - name: "6379" port: 6379 targetPort: 6379 selector: io.kompose.service: redis type: LoadBalancer --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis-udp name: redis-udp spec: ports: - name: "1234" port: 1234 protocol: UDP targetPort: 1235 selector: io.kompose.service: redis type: LoadBalancer --- apiVersion: v1 kind: Pod metadata: labels: io.kompose.service: foo name: foo spec: containers: - env: - name: GITHUB value: surajssd image: foobar name: foo ports: - containerPort: 6379 protocol: TCP - containerPort: 6379 protocol: UDP - containerPort: 3000 protocol: TCP - containerPort: 3001 protocol: TCP - containerPort: 3002 protocol: TCP - containerPort: 3003 protocol: TCP - containerPort: 3004 protocol: TCP - containerPort: 3005 protocol: TCP - containerPort: 8000 protocol: TCP - containerPort: 8080 protocol: TCP - containerPort: 8081 protocol: TCP - containerPort: 22 protocol: TCP - containerPort: 8001 protocol: TCP - containerPort: 5000 protocol: TCP - containerPort: 5001 protocol: TCP - containerPort: 5002 protocol: TCP - containerPort: 5003 protocol: TCP - containerPort: 5004 protocol: TCP - containerPort: 5005 protocol: TCP - containerPort: 5006 protocol: TCP - containerPort: 5007 protocol: TCP - containerPort: 5008 protocol: TCP - containerPort: 5009 protocol: TCP - containerPort: 5010 protocol: TCP resources: limits: memory: "10e3" restartPolicy: Never securityContext: supplementalGroups: - 1234 --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' name: redis ports: - containerPort: 6379 protocol: TCP - containerPort: 1235 protocol: UDP resources: limits: memory: "10485760e3" restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:3.0 type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis:3.0 name: "3.0" referencePolicy: type: "" ================================================ FILE: script/test/fixtures/v3.0/compose.yaml ================================================ services: redis: image: redis labels: kompose.service.type: headless healthcheck: test: echo "hello world" interval: 10s timeout: 1s retries: 5 restart: "unless-stopped" foo: image: foo:latest command: sh -c "echo Hello Foo" networks: app: {} web: {} normalized_network: {} networks: normalized_network: app: external: name: app-network web: external: name: web-network ================================================ FILE: script/test/fixtures/v3.0/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: foo name: foo spec: replicas: 1 selector: matchLabels: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - args: - sh - -c - echo Hello Foo image: foo:latest name: foo restartPolicy: Always --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: matchLabels: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: redis livenessProbe: exec: command: - echo "hello world" failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 name: redis restartPolicy: Always ================================================ FILE: script/test/fixtures/v3.0/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: redis name: redis spec: clusterIP: None ports: - name: headless port: 55555 targetPort: 0 selector: io.kompose.service: redis --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: foo name: foo spec: replicas: 1 selector: io.kompose.service: foo template: metadata: labels: io.kompose.service: foo spec: containers: - args: - sh - -c - echo Hello Foo image: ' ' name: foo restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - foo from: kind: ImageStreamTag name: foo:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: foo name: foo spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: foo:latest name: latest referencePolicy: type: "" --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: redis name: redis spec: replicas: 1 selector: io.kompose.service: redis template: metadata: labels: io.kompose.service: redis spec: containers: - image: ' ' livenessProbe: exec: command: - echo "hello world" failureThreshold: 5 periodSeconds: 10 timeoutSeconds: 1 name: redis restartPolicy: Always test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - redis from: kind: ImageStreamTag name: redis:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: redis name: redis spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: redis name: latest referencePolicy: type: "" ================================================ FILE: script/test/fixtures/vols-subpath/compose.yaml ================================================ volumes: postgres-data: services: postgres: labels: kompose.volume.subpath: test image: postgres environment: POSTGRES_USER: dumb_postgres_user POSTGRES_PASSWORD: postgres_password POSTGRES_DB: dumb_db volumes: - postgres-data:/var/lib/postgresql/data ================================================ FILE: script/test/fixtures/vols-subpath/output-k8s.yaml ================================================ --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: postgres name: postgres spec: replicas: 1 selector: matchLabels: io.kompose.service: postgres strategy: type: Recreate template: metadata: labels: io.kompose.service: postgres spec: containers: - env: - name: POSTGRES_DB value: dumb_db - name: POSTGRES_PASSWORD value: postgres_password - name: POSTGRES_USER value: dumb_postgres_user image: postgres name: postgres volumeMounts: - mountPath: /var/lib/postgresql/data name: postgres-data subPath: test restartPolicy: Always volumes: - name: postgres-data persistentVolumeClaim: claimName: postgres-data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: postgres-data name: postgres-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/vols-subpath/output-os.yaml ================================================ --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: postgres name: postgres spec: replicas: 1 selector: io.kompose.service: postgres strategy: type: Recreate template: metadata: labels: io.kompose.service: postgres spec: containers: - env: - name: POSTGRES_DB value: dumb_db - name: POSTGRES_PASSWORD value: postgres_password - name: POSTGRES_USER value: dumb_postgres_user image: ' ' name: postgres volumeMounts: - mountPath: /var/lib/postgresql/data name: postgres-data subPath: test restartPolicy: Always volumes: - name: postgres-data persistentVolumeClaim: claimName: postgres-data test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - postgres from: kind: ImageStreamTag name: postgres:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: postgres name: postgres spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: postgres name: latest referencePolicy: type: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: postgres-data name: postgres-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/volume-mounts/hostpath/docker-compose-v3.yml ================================================ services: db: image: postgres:10.1 ports: - "5432" volumes: - ./data_sux:/var/lib/postgresql/data_sux ================================================ FILE: script/test/fixtures/volume-mounts/hostpath/docker-compose.yml ================================================ services: db: image: postgres:10.1 ports: - "5432" volumes: - ./data_sux:/var/lib/postgresql/data_sux ================================================ FILE: script/test/fixtures/volume-mounts/hostpath/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "db" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "image": "postgres:10.1", "imagePullPolicy": "", "name": "db", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/postgresql/data_sux", "name": "db-hostpath0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "hostPath": { "path": "%HOSTPATH%" }, "name": "db-hostpath0" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/hostpath/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "db" ], "from": { "kind": "ImageStreamTag", "name": "db:10.1" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "db" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "volumes": [ { "name": "db-hostpath0", "hostPath": { "path": "%HOSTPATH%" } } ], "containers": [ { "name": "db", "image": " ", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "name": "db-hostpath0", "mountPath": "/var/lib/postgresql/data_sux" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "tags": [ { "name": "10.1", "annotations": null, "from": { "kind": "DockerImage", "name": "postgres:10.1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/volume-mounts/named-volume/docker-compose-v3.yml ================================================ services: db: image: postgres:10.1 ports: - "5432" labels: kompose.volume.size: 200Mi volumes: - db-data:/var/lib/postgresql/data - db-config:/var/lib/postgresql/config volumes: db-data: labels: kompose.volume.selector: db-data-dev kompose.volume.size: 500Mi ================================================ FILE: script/test/fixtures/volume-mounts/named-volume/docker-compose.yml ================================================ services: db: image: postgres:10.1 ports: - "5432" labels: kompose.volume.size: 1Gi volumes: - db-data:/var/lib/postgresql/data ================================================ FILE: script/test/fixtures/volume-mounts/named-volume/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "1Gi" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "1Gi" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "db" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "1Gi" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "image": "postgres:10.1", "imagePullPolicy": "", "name": "db", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/postgresql/data", "name": "db-data" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "db-data", "persistentVolumeClaim": { "claimName": "db-data" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "db-data", "creationTimestamp": null, "labels": { "io.kompose.service": "db-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "1Gi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/named-volume/output-k8s-v3.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "name": "db" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "db" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "200Mi" }, "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "containers": [ { "image": "postgres:10.1", "imagePullPolicy": "", "name": "db", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/lib/postgresql/data", "name": "db-data" }, { "mountPath": "/var/lib/postgresql/config", "name": "db-config" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "db-data", "persistentVolumeClaim": { "claimName": "db-data" } }, { "name": "db-config", "persistentVolumeClaim": { "claimName": "db-config" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "db-data", "creationTimestamp": null, "labels": { "io.kompose.service": "db-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "selector": { "matchLabels": { "io.kompose.service": "db-data-dev" } }, "resources": { "requests": { "storage": "500Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "db-config", "creationTimestamp": null, "labels": { "io.kompose.service": "db-config" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "200Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/named-volume/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "1Gi" } }, "spec": { "ports": [ { "name": "5432", "port": 5432, "targetPort": 5432 } ], "selector": { "io.kompose.service": "db" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%", "kompose.volume.size": "1Gi" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "db" ], "from": { "kind": "ImageStreamTag", "name": "db:10.1" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "db" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "volumes": [ { "name": "db-data", "persistentVolumeClaim": { "claimName": "db-data" } } ], "containers": [ { "name": "db", "image": " ", "ports": [ { "containerPort": 5432 } ], "resources": {}, "volumeMounts": [ { "name": "db-data", "mountPath": "/var/lib/postgresql/data" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "db", "creationTimestamp": null, "labels": { "io.kompose.service": "db" } }, "spec": { "tags": [ { "name": "10.1", "annotations": null, "from": { "kind": "DockerImage", "name": "postgres:10.1" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "db-data", "creationTimestamp": null, "labels": { "io.kompose.service": "db-data" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "1Gi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/simple-vol-mounts/docker-compose.yml ================================================ services: httpd: image: docker.io/fedora/apache ports: - "80" volumes: - /var/www/html ================================================ FILE: script/test/fixtures/volume-mounts/simple-vol-mounts/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "httpd", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "httpd" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" }, "name": "httpd" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "httpd" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" } }, "spec": { "containers": [ { "image": "docker.io/fedora/apache", "imagePullPolicy": "", "name": "httpd", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/var/www/html", "name": "httpd-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "httpd-claim0", "persistentVolumeClaim": { "claimName": "httpd-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "httpd-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/simple-vol-mounts/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "httpd", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "httpd" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "httpd", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "httpd" ], "from": { "kind": "ImageStreamTag", "name": "httpd:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "httpd" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" } }, "spec": { "volumes": [ { "name": "httpd-claim0", "persistentVolumeClaim": { "claimName": "httpd-claim0" } } ], "containers": [ { "name": "httpd", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "name": "httpd-claim0", "mountPath": "/var/www/html" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "httpd", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "docker.io/fedora/apache" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "httpd-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "httpd-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/tmpfs/docker-compose.yml ================================================ services: redis-master: image: registry.k8s.io/redis:e2e ports: - "6379" tmpfs: - /tmp:rw,noexec,nosuid ================================================ FILE: script/test/fixtures/volume-mounts/tmpfs/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "name": "redis-master" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis-master" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "containers": [ { "image": "registry.k8s.io/redis:e2e", "imagePullPolicy": "", "name": "redis-master", "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/tmp", "name": "redis-master-tmpfs0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "emptyDir": { "medium": "Memory" }, "name": "redis-master-tmpfs0" } ] } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/tmpfs/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis-master" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis-master" ], "from": { "kind": "ImageStreamTag", "name": "redis-master:e2e" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis-master" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "volumes": [ { "name": "redis-master-tmpfs0", "emptyDir": { "medium": "Memory" } } ], "containers": [ { "name": "redis-master", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {}, "volumeMounts": [ { "name": "redis-master-tmpfs0", "mountPath": "/tmp" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis-master", "creationTimestamp": null, "labels": { "io.kompose.service": "redis-master" } }, "spec": { "tags": [ { "name": "e2e", "annotations": null, "from": { "kind": "DockerImage", "name": "registry.k8s.io/redis:e2e" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/docker-compose-case.yml ================================================ services: foo: image: busybox command: sleep 3600 volumes: - /foo1 - /foo2 volumes_from: - cat bar: image: busybox command: sleep 3600 volumes: - /foo1 - /bar volumes_from: - foo cat: image: busybox command: sleep 3600 volumes: - /cat ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/docker-compose.yml ================================================ services: web: image: centos/httpd volumes: - "./app:/src/app" ports: - "3030:3000" command: nodemon -L app/bin/www nginx: restart: always image: nginx ports: - "80:80" volumes: - /www/public volumes_from: - web ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/output-k8s-case.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "bar", "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "bar" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "cat", "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "cat" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "bar", "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "bar-claim1", "persistentVolumeClaim": { "claimName": "bar-claim1" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "bar", "image": "busybox", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/foo1" }, { "name": "bar-claim1", "mountPath": "/bar" }, { "name": "foo-claim1", "mountPath": "/foo2" }, { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "bar-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "bar-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "cat", "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "volumes": [ { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "cat", "image": "busybox", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "cat-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "cat-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "Deployment", "apiVersion": "apps/v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "replicas": 1, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "foo", "image": "busybox", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/foo1" }, { "name": "foo-claim1", "mountPath": "/foo2" }, { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } }, "strategy": { "type": "Recreate" } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3030", "port": 3030, "targetPort": 3000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "name": "nginx" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "nginx" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "containers": [ { "image": "nginx", "imagePullPolicy": "", "name": "nginx", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/www/public", "name": "nginx-claim0" }, { "mountPath": "/src/app", "name": "web-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "nginx-claim0", "persistentVolumeClaim": { "claimName": "nginx-claim0" } }, { "name": "web-claim0", "persistentVolumeClaim": { "claimName": "web-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "nginx-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "args": [ "nodemon", "-L", "app/bin/www" ], "image": "centos/httpd", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 3000 } ], "resources": {}, "volumeMounts": [ { "mountPath": "/src/app", "name": "web-claim0" } ] } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": [ { "name": "web-claim0", "persistentVolumeClaim": { "claimName": "web-claim0" } } ] } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "web-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "web-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/output-os-case.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "bar", "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "bar" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "cat", "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "cat" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "ports": [ { "name": "headless", "port": 55555, "targetPort": 0 } ], "selector": { "io.kompose.service": "foo" }, "clusterIP": "None" }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "bar", "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "bar" ], "from": { "kind": "ImageStreamTag", "name": "bar:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "bar" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "bar-claim1", "persistentVolumeClaim": { "claimName": "bar-claim1" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "bar", "image": " ", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/foo1" }, { "name": "bar-claim1", "mountPath": "/bar" }, { "name": "foo-claim1", "mountPath": "/foo2" }, { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "bar", "creationTimestamp": null, "labels": { "io.kompose.service": "bar" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "busybox" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "bar-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "bar-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "cat", "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "cat" ], "from": { "kind": "ImageStreamTag", "name": "cat:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "cat" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "volumes": [ { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "cat", "image": " ", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "cat", "creationTimestamp": null, "labels": { "io.kompose.service": "cat" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "busybox" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "cat-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "cat-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "foo" ], "from": { "kind": "ImageStreamTag", "name": "foo:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "foo" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "volumes": [ { "name": "foo-claim0", "persistentVolumeClaim": { "claimName": "foo-claim0" } }, { "name": "foo-claim1", "persistentVolumeClaim": { "claimName": "foo-claim1" } }, { "name": "cat-claim0", "persistentVolumeClaim": { "claimName": "cat-claim0" } } ], "containers": [ { "name": "foo", "image": " ", "args": [ "sleep", "3600" ], "resources": {}, "volumeMounts": [ { "name": "foo-claim0", "mountPath": "/foo1" }, { "name": "foo-claim1", "mountPath": "/foo2" }, { "name": "cat-claim0", "mountPath": "/cat" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "foo", "creationTimestamp": null, "labels": { "io.kompose.service": "foo" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "busybox" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "foo-claim1", "creationTimestamp": null, "labels": { "io.kompose.service": "foo-claim1" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/volumes-from/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "80", "port": 80, "targetPort": 80 } ], "selector": { "io.kompose.service": "nginx" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "3030", "port": 3030, "targetPort": 3000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "nginx" ], "from": { "kind": "ImageStreamTag", "name": "nginx:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "nginx" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "volumes": [ { "name": "nginx-claim0", "persistentVolumeClaim": { "claimName": "nginx-claim0" } }, { "name": "web-claim0", "persistentVolumeClaim": { "claimName": "web-claim0" } } ], "containers": [ { "name": "nginx", "image": " ", "ports": [ { "containerPort": 80 } ], "resources": {}, "volumeMounts": [ { "name": "nginx-claim0", "mountPath": "/www/public" }, { "name": "web-claim0", "mountPath": "/src/app" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "nginx", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "nginx" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "nginx-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "nginx-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "%CMD%", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "type": "Recreate", "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "volumes": [ { "name": "web-claim0", "persistentVolumeClaim": { "claimName": "web-claim0" } } ], "containers": [ { "name": "web", "image": " ", "args": [ "nodemon", "-L", "app/bin/www" ], "ports": [ { "containerPort": 3000 } ], "resources": {}, "volumeMounts": [ { "name": "web-claim0", "mountPath": "/src/app" } ] } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "centos/httpd" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "web-claim0", "creationTimestamp": null, "labels": { "io.kompose.service": "web-claim0" } }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "100Mi" } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/volume-mounts/windows/compose.yaml ================================================ services: db: image: nginx:latest ports: - "80" volumes: - C:\Users\data_sux:D:\config:rw ================================================ FILE: script/test/fixtures/volume-mounts/windows/output-k8s.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: db name: db spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: db --- apiVersion: apps/v1 kind: Deployment metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: matchLabels: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: nginx:latest name: db ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: D:\config name: db-claim0 restartPolicy: Always volumes: - name: db-claim0 persistentVolumeClaim: claimName: db-claim0 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: db-claim0 name: db-claim0 spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/volume-mounts/windows/output-os.yaml ================================================ --- apiVersion: v1 kind: Service metadata: labels: io.kompose.service: db name: db spec: ports: - name: "80" port: 80 targetPort: 80 selector: io.kompose.service: db --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig metadata: labels: io.kompose.service: db name: db spec: replicas: 1 selector: io.kompose.service: db strategy: type: Recreate template: metadata: labels: io.kompose.service: db spec: containers: - image: ' ' name: db ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: D:\config name: db-claim0 restartPolicy: Always volumes: - name: db-claim0 persistentVolumeClaim: claimName: db-claim0 test: false triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - db from: kind: ImageStreamTag name: db:latest type: ImageChange --- apiVersion: image.openshift.io/v1 kind: ImageStream metadata: labels: io.kompose.service: db name: db spec: lookupPolicy: local: false tags: - from: kind: DockerImage name: nginx:latest name: latest referencePolicy: type: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: io.kompose.service: db-claim0 name: db-claim0 spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mi ================================================ FILE: script/test/fixtures/yaml-and-yml/docker-compose.yaml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/yaml-and-yml/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/yaml-and-yml/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test/fixtures/yaml-and-yml/yml/docker-compose.yml ================================================ web: image: tuna/docker-counter23 ports: - "5000:5000" links: - redis redis: image: redis:3.0 ports: - "6379" ================================================ FILE: script/test/fixtures/yaml-and-yml/yml/output-k8s-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "name": "redis" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "redis" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "image": "redis:3.0", "imagePullPolicy": "", "name": "redis", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "name": "web" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "io.kompose.service": "web" } }, "strategy": {}, "template": { "metadata": { "annotations": { "kompose.cmd": "kompose convert --stdout -j", "kompose.version": "%VERSION%" }, "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "image": "tuna/docker-counter23", "imagePullPolicy": "", "name": "web", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always", "serviceAccountName": "", "volumes": null } } }, "status": {} } ] } ================================================ FILE: script/test/fixtures/yaml-and-yml/yml/output-os-template.json ================================================ { "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "6379", "port": 6379, "targetPort": 6379 } ], "selector": { "io.kompose.service": "redis" } }, "status": { "loadBalancer": {} } }, { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "ports": [ { "name": "5000", "port": 5000, "targetPort": 5000 } ], "selector": { "io.kompose.service": "web" } }, "status": { "loadBalancer": {} } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "redis" ], "from": { "kind": "ImageStreamTag", "name": "redis:3.0" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "redis" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": " ", "ports": [ { "containerPort": 6379 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "redis", "creationTimestamp": null, "labels": { "io.kompose.service": "redis" } }, "spec": { "tags": [ { "name": "3.0", "annotations": null, "from": { "kind": "DockerImage", "name": "redis:3.0" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } }, { "kind": "DeploymentConfig", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" }, "annotations": { "kompose.cmd": "kompose --provider=openshift convert --stdout -j", "kompose.version": "%VERSION%" } }, "spec": { "strategy": { "resources": {} }, "triggers": [ { "type": "ConfigChange" }, { "type": "ImageChange", "imageChangeParams": { "automatic": true, "containerNames": [ "web" ], "from": { "kind": "ImageStreamTag", "name": "web:latest" } } } ], "replicas": 1, "test": false, "selector": { "io.kompose.service": "web" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "containers": [ { "name": "web", "image": " ", "ports": [ { "containerPort": 5000 } ], "resources": {} } ], "restartPolicy": "Always" } } }, "status": {} }, { "kind": "ImageStream", "apiVersion": "v1", "metadata": { "name": "web", "creationTimestamp": null, "labels": { "io.kompose.service": "web" } }, "spec": { "tags": [ { "name": "latest", "annotations": null, "from": { "kind": "DockerImage", "name": "tuna/docker-counter23" }, "generation": null, "importPolicy": {} } ] }, "status": { "dockerImageRepository": "" } } ] } ================================================ FILE: script/test_in_container/Dockerfile ================================================ # Copyright 2016 The Kubernetes 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. # This Dockefile creates image where all Kompose tests can be run FROM registry.centos.org/centos/centos:7 RUN yum -y update && yum -y upgrade && \ yum -y install epel-release && \ yum -y install gcc make git jq && \ yum -y clean all ENV GOPATH="/opt/go" \ GOROOT="/usr/local/go" \ GOVERSION="1.15.4" \ # KOMPOSE_TMP_SRC is where kompose source will be mounted from host KOMPOSE_TMP_SRC="/opt/tmp/kompose" ENV PATH="$PATH:$GOPATH/bin:$GOROOT/bin" \ # KOMPOSE_SRC is where kompose source will be copied when container starts (by run.sh script) # this is to ensure that we won't write anything to host volume mount KOMPOSE_SRC="$GOPATH/src/github.com/kubernetes/kompose" WORKDIR /tmp/go RUN curl https://storage.googleapis.com/golang/go$GOVERSION.linux-amd64.tar.gz | tar -xz -C /usr/local WORKDIR $KOMPOSE_SRC # This image can be run as any user RUN chmod -R ugo+rw $GOPATH COPY run.sh /opt/tools/ ENTRYPOINT ["/opt/tools/run.sh"] ================================================ FILE: script/test_in_container/run.sh ================================================ #!/bin/bash set -e # Copyright 2017 The Kubernetes 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. # Container should be started with Kompose source mounted as read-only volume $KOMPOSE_TMP_SRC # Copy Kompose sources to another directory after container starts. # We can be sure that this container won't modify any files on hosts disk. cp -r $KOMPOSE_TMP_SRC/ $(dirname $KOMPOSE_SRC) make test