diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d781d986f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Build image +FROM registry.access.redhat.com/ubi8/ubi:latest AS build_base +ARG APPNAME=move2kube +# Get Dependencies +WORKDIR /temp +ENV GOPATH=/go +ENV PATH=$GOPATH/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +RUN curl -o go.tar.gz https://dl.google.com/go/go1.15.linux-amd64.tar.gz +RUN tar -xzf go.tar.gz && mv go /usr/local/ +RUN yum install git make -y +RUN mkdir -p $GOPATH/src $GOPATH/bin && chmod -R 777 $GOPATH +ENV WORKDIR=${GOPATH}/src/${APPNAME} +WORKDIR ${WORKDIR} +COPY go.mod . +COPY go.sum . +RUN go mod download +COPY scripts/installdeps.sh scripts/installdeps.sh +RUN cd / && source ${WORKDIR}/scripts/installdeps.sh && cd - +COPY . . +# Build +RUN make build +RUN cp bin/${APPNAME} /bin/${APPNAME} + +# Run image +FROM registry.access.redhat.com/ubi8/ubi:latest +COPY misc/centos.repo /etc/yum.repos.d/centos.repo +RUN yum update -y && yum install -y podman && yum clean all +COPY --from=build_base /bin/${APPNAME} /bin/${APPNAME} +COPY --from=build_base /bin/pack /bin/pack +COPY --from=build_base /bin/kubectl /bin/kubectl +COPY --from=build_base /bin/operator-sdk /bin/operator-sdk +VOLUME ["/wksps"] +#"/var/run/docker.sock" needs to be mounted for CNB containerization to be used. +# Start app +WORKDIR /wksps +CMD [${APPNAME}] \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e9..0dae2b3f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright IBM Corporation [yyyy] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +199,4 @@ 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. + limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..bdaa439c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,154 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +BINNAME ?= move2kube +BINDIR := $(CURDIR)/bin +DISTDIR := $(CURDIR)/_dist +TARGETS := darwin/amd64 linux/amd64 + +GOPATH = $(shell go env GOPATH) +GOX = $(GOPATH)/bin/gox +GOLINT = $(GOPATH)/bin/golint +GOTEST = ${GOPATH}/bin/gotest +GOLANGCILINT = $(GOPATH)/bin/golangci-lint +GOLANGCOVER = $(GOPATH)/bin/goveralls + +PKG := ./... +LDFLAGS := -w -s + +SRC = $(shell find . -type f -name '*.go' -print) +ARCH = $(shell uname -p) +GIT_COMMIT = $(shell git rev-parse HEAD) +GIT_SHA = $(shell git rev-parse --short HEAD) +GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) +GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") + +GOGET := cd / && GO111MODULE=on go get -u + +ifdef VERSION + BINARY_VERSION = $(VERSION) +endif +BINARY_VERSION ?= ${GIT_TAG} +ifneq ($(BINARY_VERSION),) + LDFLAGS += -X github.com/konveyor/${BINNAME}/types/info.version=${BINARY_VERSION} + VERSION = $(BINARY_VERSION) +endif + +VERSION_METADATA = unreleased +ifneq ($(GIT_TAG),) + VERSION_METADATA = +endif +LDFLAGS += -X github.com/konveyor/${BINNAME}/types/info.metadata=${VERSION_METADATA} + +LDFLAGS += -X github.com/konveyor/${BINNAME}/types/info.gitCommit=${GIT_COMMIT} +LDFLAGS += -X github.com/konveyor/${BINNAME}/types/info.gitTreeState=${GIT_DIRTY} +LDFLAGS += -extldflags "-static" + +# HELP +# This will output the help for each task +.PHONY: help +help: ## This help. + @awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# -- Build -- + +.PHONY: build +build: get $(BINDIR)/$(BINNAME) ## Build go code + +$(BINDIR)/$(BINNAME): $(SRC) + @go build -tags excludecodegen -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(BINNAME) ./cmd/${BINNAME} + @cp $(BINDIR)/$(BINNAME) $(GOPATH)/bin/ + +.PHONY: get +get: go.mod + @go mod download + +.PHONY: generate +generate: + @go generate ${PKG} + +.PHONY: deps +deps: + @source scripts/installdeps.sh + +# -- Test -- + +.PHONY: test +test: ## Run tests + @go test -run . $(PKG) -race + +${GOTEST}: + ${GOGET} github.com/rakyll/gotest + +.PHONY: test-verbose +test-verbose: ${GOTEST} + @gotest -run . $(PKG) -race -v + +${GOLANGCOVER}: + ${GOGET} github.com/mattn/goveralls@v0.0.6 + +.PHONY: test-coverage +test-coverage: ${GOLANGCOVER} ## Run tests with coverage + @go test -run . $(PKG) -covermode=atomic + +${GOLANGCILINT}: + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.30.0 + +${GOLINT}: + ${GOGET} golang.org/x/lint/golint + +.PHONY: test-style +test-style: ${GOLANGCILINT} ${GOLINT} + ${GOLANGCILINT} run + ${GOLINT} ${PKG} + scripts/licensecheck.sh + +# -- Release -- + +$(GOX): + ${GOGET} github.com/mitchellh/gox@v1.0.1 + +.PHONY: build-cross +build-cross: $(GOX) clean + CGO_ENABLED=0 $(GOX) -parallel=3 -output="$(DISTDIR)/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' -ldflags '$(LDFLAGS)' ./cmd/${BINNAME} + +.PHONY: dist +dist: clean build-cross ## Build Distribution + @mkdir -p $(DISTDIR)/files + @cp -r ./{LICENSE,scripts/installdeps.sh,USAGE.md,samples} $(DISTDIR)/files/ + @cd $(DISTDIR) && \ + find * -maxdepth 1 -name "*-*" -type d \ + -exec cp -r $(DISTDIR)/files/* {} \; \ + -exec tar -zcf ${BINNAME}-${VERSION}-{}.tar.gz {} \; \ + -exec sh -c 'shasum -a 256 ${BINNAME}-${VERSION}-{}.tar.gz > ${BINNAME}-${VERSION}-{}.tar.gz.sha256sum' \; \ + -exec zip -r ${BINNAME}-${VERSION}-{}.zip {} \; \ + -exec sh -c 'shasum -a 256 ${BINNAME}-${VERSION}-{}.zip > ${BINNAME}-${VERSION}-{}.zip.sha256sum' \; + +.PHONY: clean +clean: + @rm -rf $(BINDIR) $(DISTDIR) + @go clean -cache + +.PHONY: info +info: ## Get version info + @echo "Version: ${VERSION}" + @echo "Git Tag: ${GIT_TAG}" + @echo "Git Commit: ${GIT_COMMIT}" + @echo "Git Tree State: ${GIT_DIRTY}" + +# -- Docker -- + +.PHONY: cbuild +cbuild: ## Build docker image + @docker build -t ${BINNAME}:latest . diff --git a/README.md b/README.md index 3bd1873eb..96436052f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ -# move2kube -Move2Kube is a command-line tool that accelerates the process of re-platforming to Kubernetes/Openshift. + +# Move2Kube + +Move2Kube is a command-line tool that accelerates the process of re-platforming to Kubernetes/Openshift. It does so by analysing the environment and source artifacts, and asking guidance from the user when required. + +![Pipeline](imgs/arch.png?raw=true "Pipeline") + +## Usage + +![Usage](./imgs/usage.png) + +Instructions can be found [here](./USAGE.md) + +## Setup + +1. Obtain a recent version of `golang`. Known to work with `1.15`. +1. Ensure `$GOPATH` is set. If it's not set: + 1. `mkdir ~/go` + 1. `export GOPATH=~/go` +1. Obtain this repo: + 1. `mkdir -p $GOPATH/src/` + 1. Clone this repo into the above directory. + 1. `cd $GOPATH/src/move2kube` +1. Build: `make build` +1. Run unit tests: `make test` + +## Artifacts Required + +| Source | Artifact available | Features supported | +|:-------|:-------------------|:-------------------| +| Cloud Foundry | Manifest files | Containerization options from buildpacks, Deployment artifacts | +| Cloud Foundry | Manifest files, Source code | Containerization options based on buildpack/source code, Deployment artifacts | +| Cloud Foundry | Manifest files, Source code, Access to running instance | Containerization options based on buildpack/source code, Deployment artifacts, Metadata from runtime | +| Cloud Foundry | Access to running instance | Metadata from runtime, Containerization options based on buildpack, Deployment artifacts | +| Docker Compose/Swarm | Docker compose files | Deployment artifacts | +| Docker Compose/Swarm | Docker compose files, Docker images | Deployment artifacts, Ability to enhance images to run in secure environments like Openshift. | +| Source Directories | Source code with no source metadata | Containerization options based on source code, Deployment artifacts | +| Any source | Access to target cluster | Ability to create artifacts customized for that particular cluster with the most preferred GroupVersion for the kind. | + +## Output + +* Containerization scripts + * Dockerfile + * Source 2 Image (S2I) + * Cloud Native Buildpack +* Deployment artifacts + * Kubernetes/Openshift Yamls + * Helm charts + * Operator + * Docker compose diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 000000000..a03bae6ca --- /dev/null +++ b/USAGE.md @@ -0,0 +1,21 @@ +# Move2Kube + +Move2Kube is a command-line tool that accelerates the process of re-platforming to Kubernetes/Openshift. It does so by analysing the environment and source artifacts, and asking guidance from the user when required. + +## Setup + +1. Ensure that the move2kube executable is in path. `export PATH=$PATH:$PWD` +1. To install dependencies such as `pack`, `kubectl` and `operator-sdk`, invoke `source installdeps.sh`. + +## Usage + +1. _Plan_ : Place source code in a directory say `src` and generate a plan. For example, you can use the `samples` directory. + `move2kube plan -s src` +1. _Translate_ : In the same directory, invoke the below command. + `move2kube translate` + +Note: If information about any runtime instance say cloud foundry or kubernetes cluster needs to be collected use `move2kube collect`. You can place the collected data in the `src` directory used in the plan. + +## Contact + +For more information, please contact Amith Singhee ([asinghee@in.ibm.com](mailto:asinghee@in.ibm.com)) or Ashok Pon Kumar ([ashokponkumar@in.ibm.com](mailto:ashokponkumar@in.ibm.com)) diff --git a/cmd/move2kube/collect.go b/cmd/move2kube/collect.go new file mode 100644 index 000000000..6101e6621 --- /dev/null +++ b/cmd/move2kube/collect.go @@ -0,0 +1,72 @@ +/* +Copyright IBM Corporation 2020 + +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 ( + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/konveyor/move2kube/internal/move2kube" + "github.com/konveyor/move2kube/types" +) + +var ( + annotations string +) + +const ( + annotationsFlag = "annotations" +) + +var collectCmd = &cobra.Command{ + Use: "collect", + Short: "Collect and process metadata from multiple sources.", + Long: "Collect metadata from multiple sources (cluster, image repo etc.), filter and summarize it into a yaml.", + Run: func(cmd *cobra.Command, args []string) { + if srcpath != "" { + fi, err := os.Stat(srcpath) + if os.IsNotExist(err) { + log.Fatalf("Source directory does not exist: %s.", err) + } else if err != nil { + log.Fatalf("Error while accessing directory: %s. ", srcpath) + } else if !fi.IsDir() { + log.Fatalf("Source path is a file, expected directory: %s.", srcpath) + } + } + outpath = filepath.Join(filepath.Clean(outpath), types.AppNameShort+"_collect") + if annotations == "" { + move2kube.Collect(srcpath, outpath, []string{}) + } else { + move2kube.Collect(srcpath, outpath, strings.Split(annotations, ",")) + } + log.Infof("Collect Output in [%s]. Copy this directory into the source directory to be used for planning.", outpath) + }, +} + +func init() { + viper.AutomaticEnv() + + collectCmd.Flags().StringVarP(&annotations, annotationsFlag, "a", "", "Specify annotations to select collector subset.") + collectCmd.Flags().StringVarP(&outpath, outpathFlag, "o", ".", "Specify output directory for collect.") + collectCmd.Flags().StringVarP(&srcpath, sourceFlag, "s", "", "Specify source directory for the artifacts to be considered while collecting.") + rootCmd.AddCommand(collectCmd) +} diff --git a/cmd/move2kube/move2kube.go b/cmd/move2kube/move2kube.go new file mode 100644 index 000000000..4a6dc0881 --- /dev/null +++ b/cmd/move2kube/move2kube.go @@ -0,0 +1,76 @@ +/* +Copyright IBM Corporation 2020 + +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 ( + "os" + + "github.com/konveyor/move2kube/internal/common" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + verbose bool + + planfile string + outpath string + srcpath string + name string + ignoreEnv bool +) + +const ( + nameFlag = "name" + planFlag = "plan" + sourceFlag = "source" + ignoreEnvFlag = "ignoreenv" +) + +// RootCmd root level flags and commands +var rootCmd = &cobra.Command{ + Use: "move2kube", + Short: "A tool to modernize to kubernetes/openshift", + Long: `move2kube is a tool to help optimally translate from platforms such as docker-swarm, CF to Kubernetes.`, +} + +func getRootCmd() *cobra.Command { + rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + if verbose { + log.SetLevel(log.DebugLevel) + } + return nil + } + return rootCmd +} + +func init() { + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") +} + +func main() { + assetsPath, tempPath, err := common.CreateAssetsData() + if err != nil { + log.Fatalf("Unable to create the assets directory. Error: %q", err) + } + common.TempPath = tempPath + common.AssetsPath = assetsPath + defer os.RemoveAll(tempPath) + if err := getRootCmd().Execute(); err != nil { + log.Fatalf("Error: %q", err) + } +} diff --git a/cmd/move2kube/plan.go b/cmd/move2kube/plan.go new file mode 100644 index 000000000..8495b126a --- /dev/null +++ b/cmd/move2kube/plan.go @@ -0,0 +1,81 @@ +/* +Copyright IBM Corporation 2020 + +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 ( + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/move2kube" +) + +var planCmd = &cobra.Command{ + Use: "plan", + Short: "Plan out a move", + Long: "Discover and create a plan file based on an input directory", + Run: func(cmd *cobra.Command, args []string) { + // Check if this is even a directory + fi, err := os.Stat(srcpath) + if err != nil { + log.Fatalf("Unable to access source directory : %s", err) + } + if !fi.IsDir() { + log.Fatalf("Input is a file, expected directory: %s", srcpath) + } + + fi, err = os.Stat(planfile) + if os.IsNotExist(err) { + if strings.HasSuffix(planfile, string(os.PathSeparator)) { + planfile = filepath.Join(planfile, common.DefaultPlanFile) + } else if !strings.Contains(filepath.Base(planfile), ".") { + planfile = filepath.Join(planfile, common.DefaultPlanFile) + } + } else if err != nil { + log.Fatalf("Error while accessing plan file path %s : %s ", planfile, err) + } else if fi.IsDir() { + planfile = filepath.Join(planfile, common.DefaultPlanFile) + } + + p := move2kube.CreatePlan(srcpath, name) + err = move2kube.WritePlan(p, planfile) + if err != nil { + log.Errorf("Unable to write plan file (%s) : %s", planfile, err) + } else { + log.Infof("Plan can be found at [%s].", planfile) + } + }, +} + +func init() { + viper.AutomaticEnv() + + planCmd.Flags().StringVarP(&srcpath, sourceFlag, "s", ".", "Specify source directory.") + planCmd.Flags().StringVarP(&planfile, planFlag, "p", common.DefaultPlanFile, "Specify a file path to save plan to.") + planCmd.Flags().StringVarP(&name, nameFlag, "n", common.DefaultProjectName, "Specify the project name.") + + if err := planCmd.MarkFlagRequired(sourceFlag); err != nil { + log.Fatalf("Failed to mark flag source as required.") + } + + rootCmd.AddCommand(planCmd) +} diff --git a/cmd/move2kube/translate.go b/cmd/move2kube/translate.go new file mode 100644 index 000000000..811fc544d --- /dev/null +++ b/cmd/move2kube/translate.go @@ -0,0 +1,161 @@ +/* +Copyright IBM Corporation 2020 + +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 ( + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/move2kube" + "github.com/konveyor/move2kube/internal/qaengine" + "github.com/konveyor/move2kube/types/plan" +) + +const ( + outpathFlag = "outpath" + curateFlag = "curate" + qadisablecliFlag = "qadisablecli" + qaskipFlag = "qaskip" + qaportFlag = "qaport" + qacacheFlag = "qacache" +) + +var ( + curate bool + qadisablecli bool + qaskip bool + qaport int + qacaches []string +) + +var translateCmd = &cobra.Command{ + Use: "translate", + Short: "Translate using move2kube plan", + Long: "Translate artifacts using move2kube plan", + Run: func(cmd *cobra.Command, args []string) { + // Global settings + if !ignoreEnv { + common.IgnoreEnvironment = true + } + qaengine.StartEngine(qaskip, qaport, qadisablecli) + cachepaths := []string{} + for i := len(qacaches) - 1; i >= 0; i-- { + cachepaths = append(cachepaths, qacaches[i]) + } + qaengine.AddCaches(cachepaths) + + // Parameter cleaning and curate plan + var p plan.Plan + fi, err := os.Stat(planfile) + if err == nil && fi.IsDir() { + planfile = filepath.Join(planfile, common.DefaultPlanFile) + _, err = os.Stat(planfile) + } + if err != nil { + if !cmd.Flags().Changed(planFlag) && cmd.Flags().Changed(sourceFlag) { + p = move2kube.CreatePlan(srcpath, name) + err := qaengine.SetWriteCache(filepath.Join(outpath, p.Name, common.QACacheFile)) + if err != nil { + log.Warnf("Unable to write cache : %s", err) + } + p = move2kube.CuratePlan(p) + } else { + log.Fatalf("Error while accessing plan file path %s : %s ", planfile, err) + } + } else { + p, err = move2kube.ReadPlan(planfile) + if err != nil { + log.Fatalf("Unable to read plan : %s", err) + } + if cmd.Flags().Changed(nameFlag) { + p.Name = name + } + if curate { + err = qaengine.SetWriteCache(filepath.Join(outpath, p.Name, common.QACacheFile)) + if err != nil { + log.Warnf("Unable to write cache : %s", err) + } + p = move2kube.CuratePlan(p) + } + } + if srcpath != "" { + p.Spec.Inputs.RootDir = srcpath + } + fi, err = os.Stat(p.Spec.Inputs.RootDir) + if os.IsNotExist(err) { + log.Fatalf("Source directory does not exist: %s.", err) + } else if err != nil { + log.Fatalf("Error while accessing source directory: %s. ", p.Spec.Inputs.RootDir) + } else if !fi.IsDir() { + log.Fatalf("Source path is a file, expected directory: %s.", p.Spec.Inputs.RootDir) + } + outpath = filepath.Clean(outpath) + if outpath == "." { + outpath = "" + } + outpath = filepath.Join(outpath, p.Name) + fi, err = os.Stat(outpath) + if os.IsNotExist(err) { + log.Debugf("Translated artifacts will be written to %s.", outpath) + } else if err != nil { + log.Fatalf("Error while accessing output directory: %s (%s). Exiting", outpath, err) + } else if !fi.IsDir() { + log.Fatalf("Output path is a file, expected directory: %s. Exiting", outpath) + } else { + log.Infof("Output directory exists: %s. The contents might get overwritten.", outpath) + } + err = qaengine.SetWriteCache(filepath.Join(outpath, common.QACacheFile)) + if err != nil { + log.Warnf("Unable to write cache : %s", err) + } + + // Translate + move2kube.Translate(p, outpath) + log.Infof("Translated target artifacts can be found at [%s].", outpath) + }, +} + +func init() { + viper.AutomaticEnv() + + // Basic options + translateCmd.Flags().StringVarP(&planfile, planFlag, "p", common.DefaultPlanFile, "Specify a plan file to execute.") + translateCmd.Flags().BoolVarP(&curate, curateFlag, "c", false, "Specify whether to curate the plan with a q/a.") + translateCmd.Flags().StringVarP(&srcpath, sourceFlag, "s", "", "Specify source directory to translate. If you already have a m2k.plan then this will override the rootdir value specified in that plan.") + translateCmd.Flags().StringVarP(&outpath, outpathFlag, "o", "", "Path for output. Default will be directory with the project name.") + translateCmd.Flags().StringVarP(&name, nameFlag, "n", common.DefaultProjectName, "Specify the project name.") + translateCmd.Flags().StringSliceVarP(&qacaches, qacacheFlag, "q", []string{}, "Specify qa cache file locations") + + // Advanced options + translateCmd.Flags().BoolVar(&ignoreEnv, ignoreEnvFlag, false, "Ignore data from local machine.") + + // Hidden options + translateCmd.Flags().BoolVar(&qadisablecli, qadisablecliFlag, false, "Enable/disable the QA Cli sub-system. Without this system, you will have to use the REST API to interact.") + translateCmd.Flags().BoolVar(&qaskip, qaskipFlag, false, "Enable/disable the default answers to questions posed in QA Cli sub-system. If disabled, you will have to answer the questions posed by QA during interaction.") + translateCmd.Flags().IntVar(&qaport, qaportFlag, 0, "Port for the QA service. By default it chooses a random free port.") + + _ = translateCmd.Flags().MarkHidden(qadisablecliFlag) + _ = translateCmd.Flags().MarkHidden(qaskipFlag) + _ = translateCmd.Flags().MarkHidden(qaportFlag) + + rootCmd.AddCommand(translateCmd) +} diff --git a/cmd/move2kube/version.go b/cmd/move2kube/version.go new file mode 100644 index 000000000..9ea450b1f --- /dev/null +++ b/cmd/move2kube/version.go @@ -0,0 +1,44 @@ +/* +Copyright IBM Corporation 2020 + +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" + + "github.com/konveyor/move2kube/internal/move2kube" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var long bool + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the client version information", + Long: "Print the client version information", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(move2kube.GetVersion(long)) + }, +} + +func init() { + viper.AutomaticEnv() + + versionCmd.Flags().BoolVarP(&long, "long", "l", false, "print the version details") + + rootCmd.AddCommand(versionCmd) +} diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 000000000..fbb92f4dd --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,129 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +ashokponkumar@in.ibm.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/contributing.md b/contributing.md new file mode 100644 index 000000000..5065a4960 --- /dev/null +++ b/contributing.md @@ -0,0 +1,22 @@ +# Contributing +Please read our code of conduct before contributing and make sure to follow it in all interactions with the project. + +If your proposed feature requires extensive changes/additions please contact us or raise a Github issue first. + +In order to contribute please follow this process: + +1. Fork the repo on github and clone your fork. +2. Make a new branch for your feature/bug fix. Example: `git checkout -b myfeature` +3. Make your changes and commit. + - Note: Please run `make test-style` and `make test` before making any commits to run the linters and ensure they pass build and test. This requirement allows the use of `git bisect` to find the exact commit that introduced a specific bug. +4. Make sure to format your code properly (`go fmt`) and update any relevant documentation, README.md, etc. about the changes you made. + - Note: If it is a new feature please add unit tests for the same. If it is a bug fix please add tests/test cases to catch regressions in the future. + +## Pull Request Process +Once you are ready to have your work merged into the main repo follow these steps: + +1. Fetch the latest commits from upstream. `git fetch upstream` +2. Rebase the commits from your branch onto the master branch. `git rebase upstream/master` + - Note: You will need to fix any merge conflicts that occur. +3. Once all conflicts have been resolved, push the commits to your fork (`git push`) and submit a pull request on Github. +4. The pull request may be merged after CI checks have passed and at least one maintainer has signed off on it. diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..656dd7377 --- /dev/null +++ b/go.mod @@ -0,0 +1,48 @@ +module github.com/konveyor/move2kube + +go 1.15 + +require ( + code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 // indirect + code.cloudfoundry.org/cli v7.1.0+incompatible + github.com/AlecAivazis/survey/v2 v2.1.1 + github.com/Masterminds/semver/v3 v3.1.0 + github.com/bmatcuk/doublestar v1.3.2 // indirect + github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 // indirect + github.com/cloudfoundry/bosh-cli v6.4.0+incompatible + github.com/cloudfoundry/bosh-utils v0.0.0-20200909190919-f6fb70428c10 // indirect + github.com/containers/skopeo v1.1.1-0.20200811214205-ea10e61f7d60 + github.com/cppforlife/go-patch v0.2.0 // indirect + github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24 + github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect + github.com/docker/libcompose v0.4.1-0.20171025083809-57bd716502dc + github.com/go-git/go-git/v5 v5.1.0 + github.com/gorilla/mux v1.8.0 + github.com/moby/buildkit v0.7.2 + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 + github.com/openshift/api v0.0.0-20200326160804-ecb9283fe820 // 4.1 + github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.6.0 + github.com/spf13/cast v1.3.1 + github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.7.1 + github.com/tektoncd/pipeline v0.16.3 + github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 + k8s.io/api v0.18.8 + k8s.io/apimachinery v0.19.0 + k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible + knative.dev/serving v0.17.2 +) + +replace ( + github.com/containerd/containerd v1.4.0-0 => github.com/containerd/containerd v1.4.0 + github.com/docker/cli => github.com/docker/cli v0.0.0-20200210162036-a4bedce16568 + github.com/docker/docker v0.0.0 => github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible + github.com/xeipuuv/gojsonschema => github.com/xeipuuv/gojsonschema v0.0.0-20161030231247-84d19640f6a7 // indirect + k8s.io/api => k8s.io/api v0.17.6 + k8s.io/apimachinery => k8s.io/apimachinery v0.17.6 + k8s.io/client-go => k8s.io/client-go v0.17.6 +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..2adff2998 --- /dev/null +++ b/go.sum @@ -0,0 +1,2461 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFxU= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.55.0 h1:eoz/lYxKSL4CNAiaUJ0ZfD1J3bfMYbU5B3rwM1C1EIU= +cloud.google.com/go v0.55.0/go.mod h1:ZHmoY+/lIMNkN2+fBmuTiqZ4inFhvQad8ft7MT8IV5Y= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.4.0/go.mod h1:LFrqilwgdw4X2cJS9ALgzYmMu+ULyrUN6IHV3CPK4TM= +cloud.google.com/go/pubsub v1.6.1/go.mod h1:kvW9rcn9OLEx6eTIzMBbWbpB8YsK3vu9jxgPolVz+p4= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.9.0/go.mod h1:m+/etGaqZbylxaNT876QGXqEHp4PR2Rq5GMqICWb9bU= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.11.0 h1:bSLyzhbGjLMYxCratCDRSSH7+xRGpNApTBmowDUFGLk= +cloud.google.com/go/storage v1.11.0/go.mod h1:/PAbprKS+5msVYogBmczjWalDXnQ9mr64yEq9YnyPeo= +code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 h1:/EMHruHCFXR9xClkGV/t0rmHrdhX4+trQUcBqjwc9xE= +code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= +code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6 h1:Yc9r1p21kEpni9WlG4mwOZw87TB2QlyS9sAEebZ3+ak= +code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6/go.mod h1:u5FovqC5GGAEbFPz+IdjycDA+gIjhUwqxnu0vbHwVeM= +code.cloudfoundry.org/cli v7.0.2+incompatible h1:/Ly0TD9X8QAz4l2KenlhfCNtL/RO0BPQF2+2NEcWaIY= +code.cloudfoundry.org/cli v7.0.2+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= +code.cloudfoundry.org/cli v7.1.0+incompatible h1:1Zn3I+epQBaBvnZAaTudCQQ0WdqcWtjtjEV9MBZP08Y= +code.cloudfoundry.org/cli v7.1.0+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= +code.cloudfoundry.org/cli-plugin-repo v0.0.0-20200914153654-c3a45f208585 h1:ErlA+NeoR9wkvWMapyrXI013vvc6zxy04pfoVCCzexE= +code.cloudfoundry.org/cli-plugin-repo v0.0.0-20200914153654-c3a45f208585/go.mod h1:R1EiyOAr7lW0l/YkZNqItUNZ01Q/dYUfbTn4X4Z+82M= +code.cloudfoundry.org/clock v1.0.0 h1:kFXWQM4bxYvdBw2X8BbBeXwQNgfoWv1vqAk2ZZyBN2o= +code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +code.cloudfoundry.org/go-log-cache v1.0.0 h1:e4FUVpwCDMBCFx0mSB+lGjYlVtSHT1JlK6/mxvUvTrU= +code.cloudfoundry.org/go-log-cache v1.0.0/go.mod h1:YtelJ6iHvZGxPWCIUdO98UlczOawv5jD79hxO8Ln8Pg= +code.cloudfoundry.org/go-loggregator v1.0.0 h1:z9oqyKwM5by2Q0Mvh1sP73Z/WuCqmDvrtEQDYSf2o/k= +code.cloudfoundry.org/go-loggregator v7.4.0+incompatible h1:KqZYloMQWM5Zg/BQKunOIA4OODh7djZbk48qqbowNFI= +code.cloudfoundry.org/go-loggregator v7.4.0+incompatible/go.mod h1:KPBTRqj+y738Nhf1+g4JHFaBU8j7dedirR5ETNHvMXU= +code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= +code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= +code.cloudfoundry.org/jsonry v1.1.0 h1:mbm3hZKTFgQ0mN/vmGmM8lmhMVXIG77rhYEtRrpFRd8= +code.cloudfoundry.org/jsonry v1.1.0/go.mod h1:iKx5oLd4DgbgnHF/vFuaAf9hR2ZJ9XlURHIU+yDKplk= +code.cloudfoundry.org/rfc5424 v0.0.0-20180905210152-236a6d29298a h1:8rqv2w8xEceNwckcF5ONeRt0qBHlh5bnNfFnYTrZbxs= +code.cloudfoundry.org/rfc5424 v0.0.0-20180905210152-236a6d29298a/go.mod h1:tkZo8GtzBjySJ7USvxm4E36lNQw1D3xM6oKHGqdaAJ4= +code.cloudfoundry.org/tlsconfig v0.0.0-20200131000646-bbe0f8da39b3 h1:2Qal+q+tw/DmDOoJBWwDCPE3lIJNj/1o7oMkkb2c5SI= +code.cloudfoundry.org/tlsconfig v0.0.0-20200131000646-bbe0f8da39b3/go.mod h1:eTbFJpyXRGuFVyg5+oaj9B2eIbIc+0/kZjH8ftbtdew= +code.cloudfoundry.org/workpool v0.0.0-20200131000409-2ac56b354115 h1:xm2g6uJ31UkWwrkuww9JYi13bueNh+l086tJA8Q/GM0= +code.cloudfoundry.org/workpool v0.0.0-20200131000409-2ac56b354115/go.mod h1:O9HdfntfyDvYRH9nh03XdpnGMbjyZVi8nb2Kh+6hDho= +code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d h1:M+zXqtXJqcsmpL76aU0tdl1ho23eYa4axYoM4gD62UA= +code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d/go.mod h1:YUJiVOr5xl0N/RjMxM1tHmgSpBbi5UM+KoVR5AoejO0= +code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= +contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200615190824-f8c219d2d895/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg= +contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0/go.mod h1:MjHoxkI7Ny27toPeFkRbXbzVjzIGkwOAptrAy8Mxtm8= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/exporter/stackdriver v0.12.8/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= +contrib.go.opencensus.io/exporter/stackdriver v0.13.1 h1:RX9W6FelAqTVnBi/bRXJLXr9n18v4QkQwZYIdnNS51I= +contrib.go.opencensus.io/exporter/stackdriver v0.13.1/go.mod h1:z2tyTZtPmQ2HvWH4cOmVDgtY+1lomfKdbLnkJvZdc8c= +contrib.go.opencensus.io/exporter/stackdriver v0.13.2/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg= +github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= +github.com/AkihiroSuda/containerd-fuse-overlayfs v0.0.0-20200220082720-bb896865146c/go.mod h1:K4kx7xAA5JimeQCnN+dbeLlfaBxzZLaLiDD8lusFI8w= +github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= +github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v21.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v28.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.0.0-20190123011202-457680cc0804/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= +github.com/Azure/go-autorest/autorest v0.2.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.1.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39/go.mod h1:yfGmCjKuUzk9WzubMlW2zwjhCraIc/J+M40cufdemRM= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= +github.com/GoogleCloudPlatform/testgrid v0.0.1-alpha.3/go.mod h1:f96W2HYy3tiBNV5zbbRc+NczwYHgG1PHXMQfoEWv680= +github.com/GoogleCloudPlatform/testgrid v0.0.7/go.mod h1:lmtHGBL0M/MLbu1tR9BWV7FGZ1FEFIdPqmJiHNCL7y8= +github.com/GoogleCloudPlatform/testgrid v0.0.13/go.mod h1:UlC/MvnkKjiVGijIKOHxnVyhDiTDCydw9H1XzmclQGU= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= +github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/SermoDigital/jose v0.9.1 h1:atYaHPD3lPICcbK1owly3aPm0iaJGSGPi0WD4vLznv8= +github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= +github.com/andygrunwald/go-gerrit v0.0.0-20190120104749-174420ebee6c/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= +github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA= +github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= +github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-k8s-tester v0.0.0-20190114231546-b411acf57dfe/go.mod h1:1ADF5tAtU1/mVtfMcHAYSm2fPw71DA7fFk0yed64/0I= +github.com/aws/aws-k8s-tester v0.9.3/go.mod h1:nsh1f7joi8ZI1lvR+Ron6kJM2QdCYPU/vFePghSSuTc= +github.com/aws/aws-k8s-tester v1.0.0/go.mod h1:NUNd9k43+h9O5tvwL+4N1Ctb//SapmeeFX1G0/2/0Qc= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= +github.com/aws/aws-sdk-go v1.16.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.29.32/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.30.5 h1:i+sSesaMrSxiUt3NJddOApe2mXK+VNBgfcmRTvNFrXM= +github.com/aws/aws-sdk-go v1.30.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.30.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.31.6 h1:nKjQbpXhdImctBh1e0iLg9iQW/X297LPPuY/9f92R2k= +github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.31.12 h1:SxRRGyhlCagI0DYkhOg+FgdXGXzRTE3vEX/gsgFaiKQ= +github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/bazelbuild/buildtools v0.0.0-20190917191645-69366ca98f89/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar v1.3.2 h1:mzUncgFmpzNUhIITFqGdZ8nUU0O7JTJzRO8VdkeLCSo= +github.com/bmatcuk/doublestar v1.3.2/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= +github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= +github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bwmarrin/snowflake v0.0.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 h1:vTlpHKxJqykyKdW9bkrDJNWeKNuSIAJ0TP/K4lRsz/Q= +github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1/go.mod h1:sAoA1zHCH4FJPE2gne5iBiiVG66U7Nyp6JqlOo+FEyg= +github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/clarketm/json v1.13.4/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs= +github.com/cloudevents/sdk-go v1.0.0 h1:gS5I0s2qPmdc4GBPlUmzZU7RH30BaiOdcRJ1RkXnPrc= +github.com/cloudevents/sdk-go v1.0.0/go.mod h1:3TkmM0cFqkhCHOq5JzzRU/RxRkwzoS8TZ+G448qVTog= +github.com/cloudevents/sdk-go/v2 v2.0.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= +github.com/cloudevents/sdk-go/v2 v2.1.0 h1:bmgrU8k+K2ppZ+G/q5xEQx/Xk9HRtJmkrEO3qtDO2k0= +github.com/cloudevents/sdk-go/v2 v2.1.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= +github.com/cloudfoundry/bosh-agent v0.0.60 h1:i3gitL0i8niy6QeNy4X+2vckaqKh6H7cOQF9y4ce9W0= +github.com/cloudfoundry/bosh-agent v2.333.0+incompatible h1:yyNqmvOe3mZbpnjuA1b1DHA4nSURfAGdGkpgEC/rf7c= +github.com/cloudfoundry/bosh-agent v2.333.0+incompatible/go.mod h1:7UvVn5vc/d6icLrBx6GhBlpSMwe2+x1C2A7x4TbPhiU= +github.com/cloudfoundry/bosh-cli v6.3.1+incompatible h1:t56xCMktKPfp3/HyQ1jbnLJlHwFGDo5p5WnqDXyKcx0= +github.com/cloudfoundry/bosh-cli v6.3.1+incompatible/go.mod h1:rzIB+e1sn7wQL/TJ54bl/FemPKRhXby5BIMS3tLuWFM= +github.com/cloudfoundry/bosh-cli v6.4.0+incompatible h1:1x6jneOWujeiwKoY+nSSYaZdPtyfJOBLRt36SZCudKM= +github.com/cloudfoundry/bosh-cli v6.4.0+incompatible/go.mod h1:rzIB+e1sn7wQL/TJ54bl/FemPKRhXby5BIMS3tLuWFM= +github.com/cloudfoundry/bosh-davcli v0.0.44 h1:y1G3B1+6aQ+xJ2/Bx6M8TF5wkwg9So1u5zn0IR2R4P8= +github.com/cloudfoundry/bosh-davcli v0.0.44/go.mod h1:WERSlcwbbm6fF7GJfz421BeAduzbD6JlDF7NCpYTXUE= +github.com/cloudfoundry/bosh-gcscli v0.0.18 h1:2ouLAiIcdF1pWSR+igurUPQu6fwedaXyKnpTD0RyVL8= +github.com/cloudfoundry/bosh-gcscli v0.0.18/go.mod h1:YqM92JrLuB9X8sb0aJtY2zvTgpL2wwX7n/bXGOzUGic= +github.com/cloudfoundry/bosh-s3cli v0.0.95 h1:GULEH4Kf+7YJsugACzv4tiseCKoZrQeY65u/pqOhdjM= +github.com/cloudfoundry/bosh-s3cli v0.0.95/go.mod h1:9qqPbgWJ4aXpo5It8RGpafc7S3/v+T//HLRjynVD4/8= +github.com/cloudfoundry/bosh-utils v0.0.0-20200815100254-677a8ebab4c4 h1:VU3IQZPO5GPdW2vAX3uO04aWFaSW6/lte3zjMu7E8VI= +github.com/cloudfoundry/bosh-utils v0.0.0-20200815100254-677a8ebab4c4/go.mod h1:JCrKwetZGjxbfq1U139TZuXDBfdGLtjOEAfxMWKV/QM= +github.com/cloudfoundry/bosh-utils v0.0.0-20200909190919-f6fb70428c10 h1:PjMfISPzkazTJjLN84n5wC9/TZsai5ZYll+76vKQJBk= +github.com/cloudfoundry/bosh-utils v0.0.0-20200909190919-f6fb70428c10/go.mod h1:JCrKwetZGjxbfq1U139TZuXDBfdGLtjOEAfxMWKV/QM= +github.com/cloudfoundry/config-server v0.1.21 h1:PvRL3KasbDEweX36pucSRY8h8tsgw6zG/EjOJXrrVlA= +github.com/cloudfoundry/config-server v0.1.21/go.mod h1:Y3b/MHqyp22CcG0X1qvEHG8lujoebxjD9IAslyS/Yk0= +github.com/cloudfoundry/go-socks5 v0.0.0-20180221174514-54f73bdb8a8e h1:FQdRViaoDphGRfgrotl2QGsX1gbloe57dbGBS5CG6KY= +github.com/cloudfoundry/go-socks5 v0.0.0-20180221174514-54f73bdb8a8e/go.mod h1:PXmcacyJB/pJjSxEl15IU6rEIKXrhZQRzsr0UTkgNNs= +github.com/cloudfoundry/noaa v2.1.0+incompatible h1:hr6VnM5VlYRN3YD+NmAedQLW8686sUMknOSe0mFS2vo= +github.com/cloudfoundry/noaa v2.1.0+incompatible/go.mod h1:5LmacnptvxzrTvMfL9+EJhgkUfIgcwI61BVSTh47ECo= +github.com/cloudfoundry/socks5-proxy v0.2.0 h1:ZRXcJxUqOyKmah+ytXh52K7m7S7SyuBacDUnd2g0ihU= +github.com/cloudfoundry/socks5-proxy v0.2.0/go.mod h1:0a+Ghg38uB86Dx+de84dFSkILTnBHzCpFMRnjHgSzi4= +github.com/cloudfoundry/sonde-go v0.0.0-20200416163440-a42463ba266b h1:cb7pGf4j2ZzRfnQMqVeTtMLv0l2Y81V5biZGF7zVokg= +github.com/cloudfoundry/sonde-go v0.0.0-20200416163440-a42463ba266b/go.mod h1:GS0pCHd7onIsewbw8Ue9qa9pZPv2V88cUZDttK6KzgI= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200217135630-d732e370d46d/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v0.0.0-20191219165238-8375c3424e4d/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20200219222124-986d06785c4a/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc= +github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0 h1:aWJB3lbDEaOxg1mkTBAINY2a+NsoKbAeRYefJPPRY+o= +github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20181001140422-bd77b46c8352/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41 h1:kIFnQBO7rQ0XkMe6xEwbybYHBEaWmh/f++laI6Emt7M= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/go-cni v0.0.0-20200107172653-c154a49e2c75/go.mod h1:0mg8r6FCdbxvLDqCXwAx2rO+KA37QICjKL8+wHOG5OE= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containers/common v0.18.0 h1:pZB6f17N5QV43TcT06gtx1lb0rxd/4StFdVhP9CtgQg= +github.com/containers/common v0.18.0/go.mod h1:H2Wqvx6wkqdzT4RcTCqIG4W0HSOZwUbbNiUTX1+VohU= +github.com/containers/image/v5 v5.5.1 h1:h1FCOXH6Ux9/p/E4rndsQOC4yAdRU0msRTfLVeQ7FDQ= +github.com/containers/image/v5 v5.5.1/go.mod h1:4PyNYR0nwlGq/ybVJD9hWlhmIsNra4Q8uOQX2s6E2uM= +github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= +github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/ocicrypt v1.0.2/go.mod h1:nsOhbP19flrX6rE7ieGFvBlr7modwmNjsqWarIUce4M= +github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= +github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= +github.com/containers/skopeo v1.1.1-0.20200811214205-ea10e61f7d60 h1:7LKD4qhP/EetNpBd3Btz9pV3vpo39cyak5ODGEmxvzg= +github.com/containers/skopeo v1.1.1-0.20200811214205-ea10e61f7d60/go.mod h1:JXOB7/HVOSVu5ydwFdfKYVxXgxh0kyTMIcw44WAv4tk= +github.com/containers/storage v1.20.2/go.mod h1:oOB9Ie8OVPojvoaKWEGSEtHbXUAs+tSyr7RO7ZGteMc= +github.com/containers/storage v1.21.2/go.mod h1:I1EIAA7B4OwWRSA0b4yq2AW1wjvvfcY0zLWQuwTa4zw= +github.com/containers/storage v1.22.0/go.mod h1:CsuZHF4VyzHP6CZJsn0r0sZP3pc81Me4vUQmlGvMqfU= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= +github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= +github.com/cppforlife/go-semi-semantic v0.0.0-20160921010311-576b6af77ae4 h1:J+ghqo7ZubTzelkjo9hntpTtP/9lUCWH9icEmAW+B+Q= +github.com/cppforlife/go-semi-semantic v0.0.0-20160921010311-576b6af77ae4/go.mod h1:socxpf5+mELPbosI149vWpNlHK6mbfWFxSWOoSndXR8= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= +github.com/denisenkom/go-mssqldb v0.0.0-20190111225525-2fea367d496d/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= +github.com/dgryski/go-lttb v0.0.0-20180810165845-318fcdf10a77/go.mod h1:Va5MyIzkU0rAM92tn3hb3Anb7oz7KcnixF49+2wOMe4= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20200210162036-a4bedce16568 h1:AbI1uj9w4yt6TvfKHfRu7G55KuQe7NCvWPQRKDoXggE= +github.com/docker/cli v0.0.0-20200210162036-a4bedce16568/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24 h1:bjsfAvm8BVtvQFxV7TYznmKa35J8+fmgrRJWvcS3yJo= +github.com/docker/cli v0.0.0-20200227165822-2298e6a3fe24/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v0.0.0-20200223014041-6b972e50feee/go.mod h1:xgJxuOjyp98AvnpRTR1+lGOqQ493ylRnRPmewD5GWtc= +github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.0.0-20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= +github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.0/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libcompose v0.4.1-0.20171025083809-57bd716502dc h1:N92ATQRpctiOJCFodOWiyo8fbU/3xghg/FbGjSkoATw= +github.com/docker/libcompose v0.4.1-0.20171025083809-57bd716502dc/go.mod h1:EyqDS+Iyca0hS44T7qIMTeO1EOYWWWNOGpufHu9R8cs= +github.com/docker/libnetwork v0.8.0-dev.2.0.20200226230617-d8334ccdb9be/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.8.1/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsouza/fake-gcs-server v0.0.0-20180612165233-e85be23bdaa8/go.mod h1:1/HufuJ+eaDf4KTnYdS6HJMGvMRU8d4cYTuu/1QaBbI= +github.com/fsouza/fake-gcs-server v1.19.4/go.mod h1:I0/88nHCASqJJ5M7zVF0zKODkYTcuXFW5J5yajsNJnE= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git v1.0.0 h1:YcN9iDGDoXuIw0vHls6rINwV416HYa0EB2X+RBsyYp4= +github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= +github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= +github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.46.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.55.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v0.0.0-20160411075031-7ebe0a500653/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gomodule/redigo v1.7.0/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= +github.com/google/go-containerregistry v0.0.0-20200115214256-379933c9c22b/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs= +github.com/google/go-containerregistry v0.0.0-20200123184029-53ce695e4179/go.mod h1:Wtl/v6YdQxv397EREtzwgd9+Ud7Q5D8XMbi3Zazgkrs= +github.com/google/go-containerregistry v0.0.0-20200331213917-3d03ed9b1ca2 h1:k2YJ1fw6LwICNNUQHZNp9vTtHMuVqHJtMjZOc5SDIJo= +github.com/google/go-containerregistry v0.0.0-20200331213917-3d03ed9b1ca2/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA= +github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws= +github.com/google/go-containerregistry v0.1.1/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM= +github.com/google/go-containerregistry v0.1.2 h1:YjFNKqxzWUVZND8d4ItF9wuYlE75WQfECE7yKX/Nu3o= +github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-github/v29 v29.0.3/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= +github.com/google/go-licenses v0.0.0-20191112164736-212ea350c932/go.mod h1:16wa6pRqNDUIhOtwF0GcROVqMeXHZJ7H6eGDFUh5Pfk= +github.com/google/go-licenses v0.0.0-20200227160636-0fa8c766a591/go.mod h1:JWeTIGPLQ9gF618ZOdlUitd1gRR/l99WOkHOlmR/UVA= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/licenseclassifier v0.0.0-20190926221455-842c0d70d702/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= +github.com/google/licenseclassifier v0.0.0-20200402202327-879cb1424de0/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= +github.com/google/licenseclassifier v0.0.0-20200708223521-3d09a0ea2f39/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= +github.com/google/mako v0.0.0-20190821191249-122f8dcef9e3/go.mod h1:YzLcVlL+NqWnmUEPuhS1LxDDwGO9WNbVlEXaF4IH35g= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= +github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleapis/gnostic v0.4.0 h1:BXDUo8p/DaxC+4FJY/SSx3gvnx9C1VdHNgaUkiEL5mk= +github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904= +github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= +github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.12.2 h1:D0EVSTwQoQOyfY35QNSuPJA4jpZRtkoGYWQMB7XNg5o= +github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.14.6 h1:8ERzHx8aj1Sc47mu9n/AksaKCSWrMchFtkdrS4BIj5o= +github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v0.0.0-20161215172503-049f9b42e9a5/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= +github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= +github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jenkins-x/go-scm v1.5.65/go.mod h1:MgGRkJScE/rJ30J/bXYqduN5sDPZqZFITJopsnZmTOw= +github.com/jenkins-x/go-scm v1.5.79/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= +github.com/jenkins-x/go-scm v1.5.117/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= +github.com/jessevdk/go-flags v0.0.0-20170926144705-f88afde2fa19/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jinzhu/gorm v0.0.0-20170316141641-572d0a0ab1eb/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v0.0.0-20190603042836-f5c5f50e6090/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.2/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/knative/build v0.1.2/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6iGRJe3hvV/yVXrd41yVEjxuFHxasJqDhkIjS4gk= +github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e h1:jcoUdG1TzY/M/eM5BLFLP8DJeMximx5NQYSlLL9YeWc= +github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v0.0.0-20160514122348-38ee283dabf1/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.2/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8= +github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= +github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/buildkit v0.7.2 h1:wp4R0QMXSqwjTJKhhWlJNOCSQ/OVPnsCf3N8rs09+vQ= +github.com/moby/buildkit v0.7.2/go.mod h1:D3DN/Nl4DyMH1LkwpRUJuoghqdigdXd1A6HXt5aZS40= +github.com/moby/moby v1.13.1 h1:mC5WwQwCXt/dYxZ1cIrRsnJAWw7VdtcTZUIGr4tXzOM= +github.com/moby/moby v1.13.1/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +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 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mtrmac/gpgme v0.1.2 h1:dNOmvYmsrakgW7LcgiprD0yfRuQQe8/C8F6Z+zogO3s= +github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= +github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/octago/sflags v0.2.0/go.mod h1:G0bjdxh4qPRycF74a2B8pU36iTp9QHGx0w0dFZXPt80= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20171031171758-652e15c9a27e/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20171105031654-1eecca0ba8e6/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +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.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU= +github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-tools v0.0.0-20170926011501-6d941547fa1d/go.mod h1:A9btVpZLzttF4iFaKNychhPyrhfOjJ1OF5KrA8GcLj4= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc6/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9.0.20200102164712-2b52db75279c/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9.0.20200221051241-688cf6d43cc4/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc90/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8= +github.com/opencontainers/runc v1.0.0-rc92 h1:+IczUKCRzDzFDnw99O/PAqrcBBCoRp9xN3cB1SYSNS4= +github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 h1:NhsM2gc769rVWDqJvapK37r+7+CBXI8xHhnfnt8uQsg= +github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.3.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= +github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= +github.com/opencontainers/selinux v1.5.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= +github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/openshift/api v0.0.0-20200326160804-ecb9283fe820 h1:pEmlKM0gcAPwPEOUt0JOMBd+3bDEOaSZLdvPTm2eU6E= +github.com/openshift/api v0.0.0-20200326160804-ecb9283fe820/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE= +github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= +github.com/opentracing-contrib/go-stdlib v0.0.0-20171029140428-b1a47cfbdd75/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing/opentracing-go v0.0.0-20171003133519-1361b9cd60be/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.0/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= +github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= +github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20 h1:DR5eMfe2+6GzLkVyWytdtgUxgbPiOfvKDuqityTV3y8= +github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20/go.mod h1:Y3IqE20LKprEpLkXb7gXinJf4vvDdQe/BS8E4kL/dgE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE9imhdI8YAm4oKeZISlaAR+x73zs= +github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= +github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= +github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20190706150252-9beb055b7962/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0= +github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= +github.com/satori/go.uuid v0.0.0-20160713180306-0aa62d5ddceb/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= +github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/githubv4 v0.0.0-20180925043049-51d7b505e2e9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/githubv4 v0.0.0-20191102174205-af46314aec7b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/graphql v0.0.0-20180924043259-e4a3a37e6d42/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.3/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= +github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/square/certstrap v1.2.0/go.mod h1:CUHqV+fxJW0Y5UQFnnbYwQ7bpKXO1AKbic9g73799yw= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs= +github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tedsuo/rata v1.0.0 h1:Sf9aZrYy6ElSTncjnGkyC2yuVvz5YJetBIUKJ4CmeKE= +github.com/tedsuo/rata v1.0.0/go.mod h1:X47ELzhOoLbfFIY0Cql9P6yo3Cdwf2CMX3FVZxRzJPc= +github.com/tektoncd/pipeline v0.8.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw= +github.com/tektoncd/pipeline v0.10.1/go.mod h1:D2X0exT46zYx95BU7ByM8+erpjoN7thmUBvlKThOszU= +github.com/tektoncd/pipeline v0.11.0 h1:kGeWm53R5ggajD/L2KU8kcsZ2lVd4ruN3kdqK1A/NwQ= +github.com/tektoncd/pipeline v0.11.0/go.mod h1:hlkH32S92+/UODROH0dmxzyuMxfRFp/Nc3e29MewLn8= +github.com/tektoncd/pipeline v0.13.1-0.20200625065359-44f22a067b75/go.mod h1:R5AlT46x/F8n/pFJFjZ1U1q71GWtVXgG7RZkkoRL554= +github.com/tektoncd/pipeline v0.15.2 h1:+dZyH87KKoAUIs6HAsGVT+AP4M/eV8PX2uCj9wOhGZg= +github.com/tektoncd/pipeline v0.15.2/go.mod h1:SaAxUpYbcFik2hKGOTX7NnvOa1VXTzXRGNZsY2TSwTs= +github.com/tektoncd/pipeline v0.16.3 h1:h5bJGOxbzYpa+5OwWGdlWphCEh9nmxnfVH+nhwaOFCQ= +github.com/tektoncd/pipeline v0.16.3/go.mod h1:5vn2IJH46ntWTKLkwNbtZNd38FYkNP0cBtu5sgqm5xA= +github.com/tektoncd/plumbing v0.0.0-20191216083742-847dcf196de9/go.mod h1:QZHgU07PRBTRF6N57w4+ApRu8OgfYLFNqCDlfEZaD9Y= +github.com/tektoncd/plumbing v0.0.0-20200217163359-cd0db6e567d2/go.mod h1:QZHgU07PRBTRF6N57w4+ApRu8OgfYLFNqCDlfEZaD9Y= +github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887/go.mod h1:cZPJIeTIoP7UPTxQyTQLs7VE1TiXJSNj0te+If4Q+jI= +github.com/tektoncd/plumbing/pipelinerun-logs v0.0.0-20191206114338-712d544c2c21/go.mod h1:S62EUWtqmejjJgUMOGB1CCCHRp6C706laH06BoALkzU= +github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tonistiigi/fsutil v0.0.0-20200326231323-c2c7d7b0e144/go.mod h1:0G1sLZ/0ttFf09xvh7GR4AEECnjifHRNJN/sYbLianU= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= +github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM= +github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.2.1/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/unrolled/secure v0.0.0-20180416205222-a1cf62cc2159/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= +github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= +github.com/vbauerster/mpb/v5 v5.2.2 h1:zIICVOm+XD+uV6crpSORaL6I0Q1WqOdvxZTp+r3L9cw= +github.com/vbauerster/mpb/v5 v5.2.2/go.mod h1:W5Fvgw4dm3/0NhqzV8j6EacfuTe5SvnzBRwiXxDR9ww= +github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= +github.com/vdemeester/k8s-pkg-credentialprovider v1.13.12-1/go.mod h1:Fko0rTxEtDW2kju5Ky7yFJNS3IcNvW8IPsp4/e9oev0= +github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vito/go-interact v1.0.0 h1:niLW3NjGoMWOayoR6iQ8AxWVM1Q4rR8VGZ1mt6uK3BM= +github.com/vito/go-interact v1.0.0/go.mod h1:W1mz+UVUZScRM3eUjQhEQiLDnQ+yLnXkB2rjBfGPrXg= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b h1:6cLsL+2FW6dRAdl5iMtHgRogVCff0QpRi9653YmdcJA= +github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20161030231247-84d19640f6a7 h1:lrPFiLn7iuI5BPhrGUtOINABGYcgSNM2nSB4QteC3kI= +github.com/xeipuuv/gojsonschema v0.0.0-20161030231247-84d19640f6a7/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b h1:tnWgqoOBmInkt5pbLjagwNVjjT4RdJhFHzL1ebCSRh8= +github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.1-etcd.7/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20181031231232-83304cfc808c/go.mod h1:weASp41xM3dk0YHg1s/W8ecdGP5G4teSTMBPpYAaUgA= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= +go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.9.2-0.20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= +go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go4.org v0.0.0-20190218023631-ce4c26f7be8e/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20190806162312-597adff16ade/go.mod h1:AlhUtkH4DA4asiFC5RgK7ZKmauvtkAVcy9L0epCzlWo= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180418062111-d41e8174641f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180419222023-a2a45943ae67/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828081204-131dc92a58d5 h1:7bMlihXlwJGQO5wkUzKoDE1wEy13q+lWFO6dMYQx92o= +golang.org/x/sys v0.0.0-20200828081204-131dc92a58d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112005509-a3f652f18032/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200115165105-de0b1760071a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200214144324-88be01311a71/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200303214625-2b0b585e22fe/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65 h1:1KSbntBked74wYsKq0jzXYy7ZwcjAUtrl7EmPE97Iiw= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d h1:SR+e35rACZFBohNb4Om1ibX6N3iO0FtdbwqGSuD9dBU= +golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200701000337-a32c0cb1d5b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200709181711-e327e1019dfe/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200725200936-102e7d357031/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200823205832-c024452afbcd h1:KNSumuk5eGuQV7zbOrDDZ3MIkwsQr0n5oKiH4oE0/hU= +golang.org/x/tools v0.0.0-20200823205832-c024452afbcd/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828013309-97019fc2e64b h1:TlHrnfzYWpw9fgHafO5AaiaqK81ZaJzPi+srvBuZUIQ= +golang.org/x/tools v0.0.0-20200828013309-97019fc2e64b/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181021000519-a2651947f503/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200227132054-3f1135a288c9/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200326112834-f447254575fd h1:DVCc2PgW9UrvHGZGEv4Mt3uSeQtUrrs7r8pUw+bVwWI= +google.golang.org/genproto v0.0.0-20200326112834-f447254575fd/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200528110217-3d3490e7e671/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200701001935-0939c5918c31/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200731012542-8145dea6a485/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200827165113-ac2560b5e952/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200828030656-73b5761be4c5 h1:SXvXDcZm33V0P03upM01EgN1an0ggw+yntYrnvdWbCU= +google.golang.org/genproto v0.0.0-20200828030656-73b5761be4c5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +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.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4= +gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE= +gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +helm.sh/helm/v3 v3.1.1/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +istio.io/api v0.0.0-20200512234804-e5412c253ffe/go.mod h1:kyq3g5w42zl/AKlbzDGppYpGMQYMYMyZKeq0/eexML8= +istio.io/client-go v0.0.0-20200513000250-b1d6e9886b7b/go.mod h1:aUDVNCOKom8n53OPEb7JxKucbKVNveDY4WJj7PGQb14= +istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= +istio.io/gogo-genproto v0.0.0-20191029161641-f7d19ec0141d/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= +k8s.io/api v0.0.0-20180904230853-4e7be11eab3f/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20181018013834-843ad2d9b9ae/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2/go.mod h1:AOxZTnaXR/xiarlQL0JUfwQPxjmKDvVYoRp58cA7lUo= +k8s.io/api v0.16.4/go.mod h1:AtzMnsR45tccQss5q8RnF+W8L81DH6XwXwo/joEx9u0= +k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= +k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= +k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/api v0.17.6 h1:S6qZSkjdOU0N/TYBZKoR1o7YVSiWMGFU0XXDoqs2ioA= +k8s.io/api v0.17.6/go.mod h1:1jKVwkj0UZ4huak/yRt3MFfU5wc32+B41SkNN5HhyFg= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= +k8s.io/api v0.18.1/go.mod h1:3My4jorQWzSs5a+l7Ge6JBbIxChLnY8HnuT58ZWolss= +k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= +k8s.io/api v0.18.7-rc.0/go.mod h1:v6x7KyKMJ7W/BbG7E9olOQshfszuXKKsxfnjaq+ylrk= +k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= +k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/apiextensions-apiserver v0.0.0-20190918201827-3de75813f604/go.mod h1:7H8sjDlWQu89yWB3FhZfsLyRCRLuoXoCoY5qtwW1q6I= +k8s.io/apiextensions-apiserver v0.16.4/go.mod h1:HYQwjujEkXmQNhap2C9YDdIVOSskGZ3et0Mvjcyjbto= +k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= +k8s.io/apiextensions-apiserver v0.17.6/go.mod h1:Z3CHLP3Tha+Rbav7JR3S+ye427UaJkHBomK2c4XtZ3A= +k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= +k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM= +k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20181015213631-60666be32c5d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= +k8s.io/apimachinery v0.0.0-20190816221834-a9f1d8a9c101/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d/go.mod h1:3jediapYqJ2w1BFw7lAZPCx7scubsTfosqHkhXCWJKw= +k8s.io/apimachinery v0.16.4/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= +k8s.io/apimachinery v0.16.5-beta.1/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= +k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apimachinery v0.17.6 h1:P0MNfucrmKLPsOSRbhDuG0Tplrpg7hVY4fJHh5sUIUw= +k8s.io/apimachinery v0.17.6/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.7-rc.0/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= +k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apiserver v0.0.0-20190918200908-1e17798da8c1/go.mod h1:4FuDU+iKPjdsdQSN3GsEKZLB/feQsj1y9dhhBDVV2Ns= +k8s.io/apiserver v0.16.4/go.mod h1:kbLJOak655g6W7C+muqu1F76u9wnEycfKMqbVaXIdAc= +k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= +k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= +k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/apiserver v0.17.6/go.mod h1:sAYqm8hUDNA9aj/TzqwsJoExWrxprKv0tqs/z88qym0= +k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= +k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= +k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA= +k8s.io/client-go v0.0.0-20180910083459-2cefa64ff137/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v0.0.0-20190918200256-06eb1244587a/go.mod h1:3YAcTbI2ArBRmhHns5vlHRX8YQqvkVYpz+U/N5i1mVU= +k8s.io/client-go v0.16.4/go.mod h1:ZgxhFDxSnoKY0J0U2/Y1C8obKDdlhGPZwA7oHH863Ok= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= +k8s.io/client-go v0.17.6 h1:W/JkbAcIZUPb9vENRTC75ymjQQO3qEJAZyYhOIEOifM= +k8s.io/client-go v0.17.6/go.mod h1:tX5eAbQR/Kbqv+5R93rzHQoyRnPjjW2mm9i0lXnW218= +k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= +k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= +k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= +k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= +k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible h1:A5pdeNsAyGjohGD5QrSFEto0ndr0yDWnbgZIFWvNHp0= +k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= +k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I= +k8s.io/code-generator v0.0.0-20190831074504-732c9ca86353/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= +k8s.io/code-generator v0.16.4/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY= +k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ= +k8s.io/code-generator v0.17.6/go.mod h1:iiHz51+oTx+Z9D0vB3CH3O4HDDPWrvZyUgUYaIE9h9M= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/component-base v0.0.0-20190918200425-ed2f0867c778/go.mod h1:DFWQCXgXVLiWtzFaS17KxHdlUeUymP7FLxZSkmL9/jU= +k8s.io/component-base v0.16.4/go.mod h1:GYQ+4hlkEwdlpAp59Ztc4gYuFhdoZqiAJD1unYDJ3FM= +k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= +k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/component-base v0.17.6/go.mod h1:jgRLWl0B0rOzFNtxQ9E4BphPmDqoMafujdau6AdG2Xo= +k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= +k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= +k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= +k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190306031000-7a1b7fb0289f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20191108084044-e500ee069b5c/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk= +k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.14.7/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= +k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= +k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw= +k8s.io/test-infra v0.0.0-20181019233642-2e10a0bbe9b3/go.mod h1:2NzXB13Ji0nqpyublHeiPC4FZwU0TknfvyaaNfl/BTA= +k8s.io/test-infra v0.0.0-20191212060232-70b0b49fe247/go.mod h1:d8SKryJBXAwfCFVL4wieRez47J2NOOAb9d029sWLseQ= +k8s.io/test-infra v0.0.0-20200407001919-bc7f71ef65b8/go.mod h1:/WpJWcaDvuykB322WXP4kJbX8IpalOzuPxA62GpwkJk= +k8s.io/test-infra v0.0.0-20200514184223-ba32c8aae783/go.mod h1:bW6thaPZfL2hW7ecjx2WYwlP9KQLM47/xIJyttkVk5s= +k8s.io/test-infra v0.0.0-20200617221206-ea73eaeab7ff/go.mod h1:L3+cRvwftUq8IW1TrHji5m3msnc4uck/7LsE/GR/aZk= +k8s.io/test-infra v0.0.0-20200630233406-1dca6122872e/go.mod h1:L3+cRvwftUq8IW1TrHji5m3msnc4uck/7LsE/GR/aZk= +k8s.io/test-infra v0.0.0-20200803112140-d8aa4e063646/go.mod h1:rtUd2cOFwT0aBma1ld6W40F7PuVVw4ELLSFlz9ZEmv8= +k8s.io/test-infra v0.0.0-20200828131253-b23899a92dfa/go.mod h1:exGUBhfuK+dvNp2knbElr498c8UY2UN00oKgfhs7Llo= +k8s.io/utils v0.0.0-20181019225348-5e321f9a457c/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200124190032-861946025e34 h1:HjlUD6M0K3P8nRXmr2B9o4F9dUy9TCj/aEpReeyi6+k= +k8s.io/utils v0.0.0-20200124190032-861946025e34/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +knative.dev/caching v0.0.0-20190719140829-2032732871ff/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= +knative.dev/caching v0.0.0-20200116200605-67bca2c83dfa/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= +knative.dev/caching v0.0.0-20200630172829-a78409990d76/go.mod h1:iyrTBBqVs1H+bmj+mqyGEgNiLBzuGsY1LslIayGy0OE= +knative.dev/caching v0.0.0-20200824162048-b1979a3a7550/go.mod h1:9UKPHvHm1XlyWf2hXNKWCT90EEkRUiyn9lXod7CN2PY= +knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= +knative.dev/eventing-contrib v0.11.2/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= +knative.dev/networking v0.0.0-20200630191330-5080f859c17d h1:/U/lrkoEsrnDc3LHpaKHhy0uRVXpxOpH8eGVdDltsLE= +knative.dev/networking v0.0.0-20200630191330-5080f859c17d/go.mod h1:76Bt5r+xMgNrjwIjSvCvuQwbYG23cTFDIDrxU1ECA54= +knative.dev/networking v0.0.0-20200817055406-2b6d120d60b8 h1:Js1L84C4Q0Xw0oWHLE9iVW3rWq5VCcsr3dJ/6Zco1tI= +knative.dev/networking v0.0.0-20200817055406-2b6d120d60b8/go.mod h1:ZewGJAElO4qPOeZTKuLIO3NQNGAkqcQVu64gHOSiPPg= +knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= +knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= +knative.dev/pkg v0.0.0-20200207155214-fef852970f43/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= +knative.dev/pkg v0.0.0-20200428194351-90fc61bae7f7/go.mod h1:o+e8OVEJKIuvXPsGVPIautjXgs05xbos7G+QMRjuUps= +knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2/go.mod h1:Q6sL35DdGs8hIQZKdaCXJGgY8f90BmNBKSb8z6d/BTM= +knative.dev/pkg v0.0.0-20200515002500-16d7b963416f/go.mod h1:tMOHGbxtRz8zYFGEGpV/bpoTEM1o89MwYFC4YJXl3GY= +knative.dev/pkg v0.0.0-20200528142800-1c6815d7e4c9/go.mod h1:QgNZTxnwpB/oSpNcfnLVlw+WpEwwyKAvJlvR3hgeltA= +knative.dev/pkg v0.0.0-20200630170034-2c1a029eb97f/go.mod h1:7T15JzvjKXWnvIKcohz4brrsVq8jvwAcJwWY9xigAc0= +knative.dev/pkg v0.0.0-20200702222342-ea4d6e985ba0 h1:+k2ADqygEgy5BIEahUUEgdY3LbQkaRxbObIO1ZjQswE= +knative.dev/pkg v0.0.0-20200702222342-ea4d6e985ba0/go.mod h1:7T15JzvjKXWnvIKcohz4brrsVq8jvwAcJwWY9xigAc0= +knative.dev/pkg v0.0.0-20200711004937-22502028e31a/go.mod h1:AqAJV6rYi8IGikDjJ/9ZQd9qKdkXVlesVnVjwx62YB8= +knative.dev/pkg v0.0.0-20200811165506-f6ed1766e8ee/go.mod h1:udIbxBS/SJCL4sqnCG8HZArez9HjWmeqJCaVJP/h32I= +knative.dev/pkg v0.0.0-20200812224206-44c860147a87/go.mod h1:udIbxBS/SJCL4sqnCG8HZArez9HjWmeqJCaVJP/h32I= +knative.dev/pkg v0.0.0-20200824160247-5343c1d19369 h1:1hf89VqyCXYwYUueJ+zMrbvbm9oJy46O8cdWkrWFLdQ= +knative.dev/pkg v0.0.0-20200824160247-5343c1d19369/go.mod h1:7Ezp2dJ/stJ4tyrPJ5Loetb+iiwiXNAt+YLrtzE/y4E= +knative.dev/serving v0.16.0 h1:vtYJRsCHSl1PkyXDoDTJ0ksJIlAEEbxyBHqURbbgC5g= +knative.dev/serving v0.16.0/go.mod h1:XG6NOSbtstohsGGl0UYcTazPDMLWDk8W8F0Cd+W9ioI= +knative.dev/serving v0.17.2 h1:5sHdaWhMdoZW+1VKCxuJODcXZCcNSCK/lOU/+ypWkxk= +knative.dev/serving v0.17.2/go.mod h1:uiEM7RmJkA12Ha9VaihrF7kxqCy5faHhQXfYg5isLEQ= +knative.dev/test-infra v0.0.0-20200407185800-1b88cb3b45a5/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ= +knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55/go.mod h1:WqF1Azka+FxPZ20keR2zCNtiQA1MP9ZB4BH4HuI+SIU= +knative.dev/test-infra v0.0.0-20200513011557-d03429a76034/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE= +knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9/go.mod h1:A5b2OAXTOeHT3hHhVQm3dmtbuWvIDP7qzgtqxA3/2pE= +knative.dev/test-infra v0.0.0-20200630141629-15f40fe97047/go.mod h1:30tMsI1VXrG2m4ut7CFZbLg1VbcRsslPfGU+GWILm6E= +knative.dev/test-infra v0.0.0-20200707183444-aed09e56ddc7/go.mod h1:RjYAhXnZqeHw9+B0zsbqSPlae0lCvjekO/nw5ZMpLCs= +knative.dev/test-infra v0.0.0-20200811030605-72f8c9f3e933/go.mod h1:Pmg2c7Z7q7BGFUV/GOpU5BlrD3ePJft4MPqx8AYBplc= +knative.dev/test-infra v0.0.0-20200813220834-388e55a496cf/go.mod h1:Pmg2c7Z7q7BGFUV/GOpU5BlrD3ePJft4MPqx8AYBplc= +knative.dev/test-infra v0.0.0-20200828171708-f68cb78c80a9/go.mod h1:0uZ4scq51k4xUTJe8zK+wnODCEM+v7pKM6iTKk0Oqo8= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= +mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU= +pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/boskos v0.0.0-20200526191642-45fc818e2d00/go.mod h1:L1ubP7d1CCMSQSjKiZv6dGbh7b4kfoG+dFPj8cfYDnI= +sigs.k8s.io/boskos v0.0.0-20200530174753-71e795271860/go.mod h1:L1ubP7d1CCMSQSjKiZv6dGbh7b4kfoG+dFPj8cfYDnI= +sigs.k8s.io/boskos v0.0.0-20200617235605-f289ba6555ba/go.mod h1:ZO5RV+VxJS9mb6DvZ1yAjywoyq/wQ8b0vDoZxcIA5kE= +sigs.k8s.io/boskos v0.0.0-20200729174948-794df80db9c9/go.mod h1:ZO5RV+VxJS9mb6DvZ1yAjywoyq/wQ8b0vDoZxcIA5kE= +sigs.k8s.io/boskos v0.0.0-20200819010710-984516eae7e8/go.mod h1:ZO5RV+VxJS9mb6DvZ1yAjywoyq/wQ8b0vDoZxcIA5kE= +sigs.k8s.io/controller-runtime v0.3.0/go.mod h1:Cw6PkEg0Sa7dAYovGT4R0tRkGhHXpYijwNxYhAnAZZk= +sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= +sigs.k8s.io/controller-runtime v0.5.4/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA= +sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.1-0.20200706213357-43c19bbb7fba h1:AAbnc5KQuTWKuh2QSnyghKIOTFzB0Jayv7/OFDn3Cy4= +sigs.k8s.io/structured-merge-diff/v3 v3.0.1-0.20200706213357-43c19bbb7fba/go.mod h1:V06abazjHneE37ZdSY/UUwPVgcJMKI/jU5XGUjgIKoc= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= +vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= +vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/imgs/arch.png b/imgs/arch.png new file mode 100644 index 000000000..188d6bf13 Binary files /dev/null and b/imgs/arch.png differ diff --git a/imgs/usage.png b/imgs/usage.png new file mode 100644 index 000000000..ab5bfcb03 Binary files /dev/null and b/imgs/usage.png differ diff --git a/internal/apiresource/apiresource.go b/internal/apiresource/apiresource.go new file mode 100644 index 000000000..55d47ac41 --- /dev/null +++ b/internal/apiresource/apiresource.go @@ -0,0 +1,184 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + selector = "io." + types.AppName + ".service" +) + +// IAPIResource deines the interface to be deined for a new api resource +type IAPIResource interface { + GetSupportedKinds() []string + CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object + ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) // Return nil if not supported +} + +// APIResource defines functions that are reusable across the api resources +type APIResource struct { + IAPIResource + cluster collecttypes.ClusterMetadataSpec + cachedobjs []runtime.Object +} + +// SetClusterContext sets the cluster context +func (o *APIResource) SetClusterContext(cluster collecttypes.ClusterMetadataSpec) { + o.cluster = cluster +} + +// LoadResources loads the resources +func (o *APIResource) LoadResources(objs []runtime.Object) []runtime.Object { //Returns resources it could not handle + ignoredResources := []runtime.Object{} + for _, obj := range objs { + if obj == nil { + continue + } + if !o.loadResource(obj, objs) { + ignoredResources = append(ignoredResources, obj) + } + } + return ignoredResources +} + +// GetUpdatedResources converts IR to a runtime object +func (o *APIResource) GetUpdatedResources(ir irtypes.IR) []runtime.Object { + objs := o.CreateNewResources(ir, o.getClusterSupportedKinds()) + for _, obj := range objs { + if !o.loadResource(obj, objs) { + log.Errorf("Object created seems to be of an incompatible type : %+v", obj) + } + } + return o.cachedobjs +} + +func (o *APIResource) isSupportedKind(obj runtime.Object) bool { + kind := obj.GetObjectKind().GroupVersionKind().Kind + return common.IsStringPresent(o.GetSupportedKinds(), kind) +} + +func (o *APIResource) loadResource(obj runtime.Object, otherobjs []runtime.Object) bool { //Returns resources it could not handle + if !o.isSupportedKind(obj) { + return false + } + supportedobjs, done := o.ConvertToClusterSupportedKinds(obj, o.getClusterSupportedKinds(), otherobjs) + if !done { + return false + } + if o.cachedobjs == nil { + o.cachedobjs = supportedobjs + return true + } + for _, supportedobj := range supportedobjs { + objs := []runtime.Object{} + merged := false + for _, cachedobj := range o.cachedobjs { + var mergedobj runtime.Object = nil + if o.isSameResource(cachedobj, supportedobj) { + mergedobj = o.merge(cachedobj, supportedobj) + } + if mergedobj != nil { + objs = append(objs, mergedobj) + merged = true + } else { + objs = append(objs, cachedobj) + } + } + if !merged { + objs = append(objs, supportedobj) + } + o.cachedobjs = objs + } + return true +} + +// Could be different versions, but will still be marked as duplicate +func (o *APIResource) isSameResource(obj1 runtime.Object, obj2 runtime.Object) bool { + if o.shareSameID(obj1, obj2) && obj1.GetObjectKind().GroupVersionKind().GroupKind().Empty() != obj2.GetObjectKind().GroupVersionKind().GroupKind().Empty() { + return true + } + return false +} + +func (o *APIResource) shareSameID(obj1 runtime.Object, obj2 runtime.Object) bool { + obj1id := o.getObjectID(obj1) + obj2id := o.getObjectID(obj2) + if obj1id == "" || obj2id == "" || obj1id != obj2id { + return false + } + return true +} + +func getServiceLabels(name string) map[string]string { + return map[string]string{selector: name} +} + +// getAnnotations configures annotations +func getAnnotations(service irtypes.Service) map[string]string { + + annotations := map[string]string{} + for key, value := range service.Annotations { + annotations[key] = value + } + + return annotations +} + +func (o *APIResource) merge(obj1 runtime.Object, obj2 runtime.Object) runtime.Object { + if obj1.GetObjectKind().GroupVersionKind().Kind == obj2.GetObjectKind().GroupVersionKind().Kind { + reflect.ValueOf(obj2).MethodByName("DeepCopyInto").Call([]reflect.Value{reflect.ValueOf(obj1)}) + return obj1 + } + return nil +} + +func (o *APIResource) getObjectID(obj runtime.Object) string { + k8sObjValue := reflect.ValueOf(obj).Elem() + objMeta, ok := k8sObjValue.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta) + if !ok { + log.Errorf("Failed to retrieve object metadata") + } + return objMeta.GetNamespace() + objMeta.GetName() +} + +func (o *APIResource) getClusterSupportedKinds() []string { + kinds := o.IAPIResource.GetSupportedKinds() + supportedKinds := []string{} + for _, kind := range kinds { + if o.cluster.GetSupportedVersions(kind) != nil { + supportedKinds = append(supportedKinds, kind) + } + } + return supportedKinds +} + +func getPodLabels(name string, networks []string) map[string]string { + labels := getServiceLabels(name) + networklabels := getNetworkPolicyLabels(networks) + return common.MergeStringMaps(labels, networklabels) +} diff --git a/internal/apiresource/deployment.go b/internal/apiresource/deployment.go new file mode 100644 index 000000000..4ec5b8808 --- /dev/null +++ b/internal/apiresource/deployment.go @@ -0,0 +1,449 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" + + okdappsv1 "github.com/openshift/api/apps/v1" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/konveyor/move2kube/internal/common" + internaltypes "github.com/konveyor/move2kube/internal/types" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +//TODO: Add support for replicaset, cronjob ad statefulset + +const ( + // podKind defines Pod Kind + podKind string = "Pod" + // jobKind defines Job Kind + jobKind string = "Job" + // deploymentKind defines Deployment Kind + deploymentKind string = "Deployment" + // deploymentConfigKind defines DeploymentConfig Kind + deploymentConfigKind string = "DeploymentConfig" + // replicationControllerKind defines ReplicationController Kind + replicationControllerKind string = "ReplicationController" + // daemonSetKind defines DaemonSet Kind + daemonSetKind string = "DaemonSet" +) + +// Deployment handles all objects like a Deployment +type Deployment struct { + ClusterSpec collecttypes.ClusterMetadataSpec +} + +// GetSupportedKinds returns kinds supported by the deployment +func (d *Deployment) GetSupportedKinds() []string { + return []string{podKind, jobKind, deploymentKind, deploymentConfigKind, replicationControllerKind} +} + +// CreateNewResources converts ir to runtime object +func (d *Deployment) CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + for _, service := range ir.Services { + var obj runtime.Object + if service.Daemon { + if common.IsStringPresent(supportedKinds, daemonSetKind) { + obj = d.createDaemonSet(service) + } + } else if service.RestartPolicy == corev1.RestartPolicyNever || service.RestartPolicy == corev1.RestartPolicyOnFailure { + if common.IsStringPresent(supportedKinds, jobKind) { + obj = d.createJob(service) + } else if common.IsStringPresent(supportedKinds, podKind) { + pod := d.createPod(service) + pod.Spec.RestartPolicy = corev1.RestartPolicyOnFailure + obj = pod + } + } else if common.IsStringPresent(supportedKinds, deploymentConfigKind) { + obj = d.createDeploymentConfig(service) + } else if common.IsStringPresent(supportedKinds, deploymentKind) { + obj = d.createDeployment(service) + } else if common.IsStringPresent(supportedKinds, replicationControllerKind) { + obj = d.createReplicationController(service) + } else if common.IsStringPresent(supportedKinds, podKind) { + obj = d.createPod(service) + } else { + log.Errorf("Could not find a valid resource type in cluster to create a deployment") + } + if obj != nil { + objs = append(objs, obj) + } + } + return objs +} + +// ConvertToClusterSupportedKinds converts objects to kind supported by the cluster +func (d *Deployment) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if d1, ok := obj.(*appsv1.DaemonSet); ok { + if common.IsStringPresent(supportedKinds, daemonSetKind) { + return []runtime.Object{d1}, true + } + return nil, false + } + if d1, ok := obj.(*corev1.Pod); ok && (d1.Spec.RestartPolicy == corev1.RestartPolicyOnFailure || d1.Spec.RestartPolicy == corev1.RestartPolicyNever) { + if common.IsStringPresent(supportedKinds, jobKind) { + return []runtime.Object{d.podToJob(*d1)}, true + } + return []runtime.Object{obj}, true + } else if d1, ok := obj.(*batchv1.Job); ok && !common.IsStringPresent(supportedKinds, jobKind) { + if !common.IsStringPresent(supportedKinds, jobKind) && common.IsStringPresent(supportedKinds, podKind) { + return []runtime.Object{d.toPod(d1.ObjectMeta, d1.Spec.Template.Spec, corev1.RestartPolicyOnFailure)}, true + } + log.Warnf("Both Job and Pod not supported. No other valid way to translate this object. : %+v", obj) + return []runtime.Object{obj}, true + } + if common.IsStringPresent(supportedKinds, deploymentConfigKind) { + if d1, ok := obj.(*appsv1.Deployment); ok { + return []runtime.Object{d.toDeploymentConfig(d1.ObjectMeta, d1.Spec.Template.Spec, *d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*corev1.ReplicationController); ok { + return []runtime.Object{d.toDeploymentConfig(d1.ObjectMeta, d1.Spec.Template.Spec, *d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*corev1.Pod); ok { + var replicas int32 = 2 + return []runtime.Object{d.toDeploymentConfig(d1.ObjectMeta, d1.Spec, replicas)}, true + } + return []runtime.Object{obj}, true + } + if common.IsStringPresent(supportedKinds, deploymentKind) { + if d1, ok := obj.(*okdappsv1.DeploymentConfig); ok { + return []runtime.Object{d.toDeployment(d1.ObjectMeta, d1.Spec.Template.Spec, d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*corev1.ReplicationController); ok { + return []runtime.Object{d.toDeployment(d1.ObjectMeta, d1.Spec.Template.Spec, *d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*corev1.Pod); ok { + var replicas int32 = 2 + return []runtime.Object{d.toDeployment(d1.ObjectMeta, d1.Spec, replicas)}, true + } else { + return []runtime.Object{obj}, true + } + } + if common.IsStringPresent(supportedKinds, replicationControllerKind) { + if d1, ok := obj.(*okdappsv1.DeploymentConfig); ok { + return []runtime.Object{d.toReplicationController(d1.ObjectMeta, d1.Spec.Template.Spec, d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*appsv1.Deployment); ok { + return []runtime.Object{d.toReplicationController(d1.ObjectMeta, d1.Spec.Template.Spec, *d1.Spec.Replicas)}, true + } else if d1, ok := obj.(*corev1.Pod); ok { + var replicas int32 = 2 + return []runtime.Object{d.toReplicationController(d1.ObjectMeta, d1.Spec, replicas)}, true + } else { + return []runtime.Object{obj}, true + } + } + if common.IsStringPresent(supportedKinds, podKind) { + if d1, ok := obj.(*okdappsv1.DeploymentConfig); ok { + return []runtime.Object{d.toPod(d1.ObjectMeta, d1.Spec.Template.Spec, corev1.RestartPolicyAlways)}, true + } else if d1, ok := obj.(*appsv1.Deployment); ok { + return []runtime.Object{d.toPod(d1.ObjectMeta, d1.Spec.Template.Spec, corev1.RestartPolicyAlways)}, true + } else if d1, ok := obj.(*corev1.ReplicationController); ok { + return []runtime.Object{d.toPod(d1.ObjectMeta, d1.Spec.Template.Spec, corev1.RestartPolicyAlways)}, true + } else { + return []runtime.Object{obj}, true + } + } + return nil, false +} + +// GetNameAndPodSpec returns the name and podspec used by the deployment +func (d *Deployment) GetNameAndPodSpec(obj runtime.Object) (name string, podSpec v1.PodSpec, err error) { + if d1, ok := obj.(*okdappsv1.DeploymentConfig); ok { + return d1.Name, d1.Spec.Template.Spec, nil + } else if d1, ok := obj.(*appsv1.Deployment); ok { + return d1.Name, d1.Spec.Template.Spec, nil + } else if d1, ok := obj.(*corev1.ReplicationController); ok { + return d1.Name, d1.Spec.Template.Spec, nil + } else if d1, ok := obj.(*corev1.Pod); ok { + return d1.Name, d1.Spec, nil + } else if d1, ok := obj.(*batchv1.Job); ok { + return d1.Name, d1.Spec.Template.Spec, nil + } else if d1, ok := obj.(*appsv1.DaemonSet); ok { + return d1.Name, d1.Spec.Template.Spec, nil + } + return "", v1.PodSpec{}, fmt.Errorf("Incompatible object type") +} + +// Create section + +func (d *Deployment) createDeployment(service irtypes.Service) *appsv1.Deployment { + + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + podSpec := service.PodSpec + podSpec = d.convertVolumesKindsByPolicy(podSpec) + podSpec.RestartPolicy = corev1.RestartPolicyAlways + log.Debugf("Created deployment for %s", service.Name) + return d.toDeployment(meta, podSpec, int32(service.Replicas)) +} + +func (d *Deployment) createDeploymentConfig(service irtypes.Service) *okdappsv1.DeploymentConfig { + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + podSpec := service.PodSpec + podSpec = d.convertVolumesKindsByPolicy(podSpec) + podSpec.RestartPolicy = corev1.RestartPolicyAlways + log.Debugf("Created DeploymentConfig for %s", service.Name) + return d.toDeploymentConfig(meta, podSpec, int32(service.Replicas)) +} + +// createReplicationController initializes Kubernetes ReplicationController object +func (d *Deployment) createReplicationController(service internaltypes.Service) *corev1.ReplicationController { + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + podSpec := service.PodSpec + podSpec = d.convertVolumesKindsByPolicy(podSpec) + podSpec.RestartPolicy = corev1.RestartPolicyAlways + log.Debugf("Created DeploymentConfig for %s", service.Name) + return d.toReplicationController(meta, podSpec, int32(service.Replicas)) +} + +func (d *Deployment) createPod(service irtypes.Service) *corev1.Pod { + podSpec := service.PodSpec + podSpec = d.convertVolumesKindsByPolicy(podSpec) + podSpec.RestartPolicy = corev1.RestartPolicyAlways + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + return d.toPod(meta, podSpec, podSpec.RestartPolicy) +} + +func (d *Deployment) createDaemonSet(service irtypes.Service) *appsv1.DaemonSet { + podSpec := service.PodSpec + podSpec = d.convertVolumesKindsByPolicy(podSpec) + podSpec.RestartPolicy = corev1.RestartPolicyAlways + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + pod := appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{ + Kind: daemonSetKind, + APIVersion: metav1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta, + Spec: appsv1.DaemonSetSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: meta, + Spec: podSpec, + }, + }, + } + return &pod +} + +func (d *Deployment) createJob(service irtypes.Service) *batchv1.Job { + podspec := service.PodSpec + podspec = d.convertVolumesKindsByPolicy(podspec) + podspec.RestartPolicy = corev1.RestartPolicyOnFailure + meta := metav1.ObjectMeta{ + Name: service.Name, + Labels: getPodLabels(service.Name, service.Networks), + Annotations: getAnnotations(service), + } + pod := batchv1.Job{ + TypeMeta: metav1.TypeMeta{ + Kind: jobKind, + APIVersion: batchv1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: meta, + Spec: podspec, + }, + }, + } + return &pod +} + +// Conversions section + +func (d *Deployment) toDeploymentConfig(meta metav1.ObjectMeta, podspec corev1.PodSpec, replicas int32) *okdappsv1.DeploymentConfig { + podspec = d.convertVolumesKindsByPolicy(podspec) + triggerPolicies := []okdappsv1.DeploymentTriggerPolicy{{ + Type: okdappsv1.DeploymentTriggerOnConfigChange, + }} + for _, container := range podspec.Containers { + _, tag := common.GetImageNameAndTag(container.Image) + triggerPolicies = append(triggerPolicies, okdappsv1.DeploymentTriggerPolicy{ + Type: okdappsv1.DeploymentTriggerOnImageChange, + ImageChangeParams: &okdappsv1.DeploymentTriggerImageChangeParams{ + Automatic: true, + ContainerNames: []string{container.Name}, + From: corev1.ObjectReference{ + Name: meta.Name + ":" + tag, + Kind: "ImageStreamTag", + }, + }, + }) + } + dc := &okdappsv1.DeploymentConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentConfigKind, + APIVersion: okdappsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta, + Spec: okdappsv1.DeploymentConfigSpec{ + Replicas: int32(replicas), + Selector: getServiceLabels(meta.Name), + Template: &corev1.PodTemplateSpec{ + ObjectMeta: meta, + Spec: podspec, // obj.Spec.Template.Spec, + }, + Triggers: triggerPolicies, + }, + } + return dc +} + +func (d *Deployment) toDeployment(meta metav1.ObjectMeta, podspec corev1.PodSpec, replicas int32) *appsv1.Deployment { + podspec = d.convertVolumesKindsByPolicy(podspec) + dc := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: appsv1.SchemeGroupVersion.String(), //"apps/v1", + }, + ObjectMeta: meta, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: getServiceLabels(meta.Name), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: meta, + Spec: podspec, + }, + }, + } + return dc +} + +// toReplicationController initializes Kubernetes ReplicationController object +func (d *Deployment) toReplicationController(meta metav1.ObjectMeta, podspec corev1.PodSpec, replicas int32) *corev1.ReplicationController { + podspec = d.convertVolumesKindsByPolicy(podspec) + nReplicas := int32(replicas) + rc := &corev1.ReplicationController{ + TypeMeta: metav1.TypeMeta{ + Kind: replicationControllerKind, + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta, + Spec: corev1.ReplicationControllerSpec{ + Replicas: &nReplicas, + Selector: getServiceLabels(meta.Name), + Template: &corev1.PodTemplateSpec{ + ObjectMeta: meta, + Spec: podspec, + }, + }, + } + return rc +} + +func (d *Deployment) podToJob(obj corev1.Pod) *batchv1.Job { + podspec := obj.Spec + podspec = d.convertVolumesKindsByPolicy(podspec) + podspec.RestartPolicy = corev1.RestartPolicyOnFailure + pod := batchv1.Job{ + TypeMeta: metav1.TypeMeta{ + Kind: jobKind, + APIVersion: batchv1.SchemeGroupVersion.String(), + }, + ObjectMeta: obj.ObjectMeta, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: obj.ObjectMeta, + Spec: podspec, + }, + }, + } + return &pod +} + +func (d *Deployment) toPod(meta metav1.ObjectMeta, podspec corev1.PodSpec, restartPolicy corev1.RestartPolicy) *corev1.Pod { + podspec = d.convertVolumesKindsByPolicy(podspec) + podspec.RestartPolicy = restartPolicy + pod := corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: podKind, + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta, + Spec: podspec, + } + return &pod +} + +//Volumes and volume mounts of all containers are translated as follows: +//1. Each container's volume mount list and corresponding volumes are translated +//2. Unreferenced volumes are discarded +func (d *Deployment) convertVolumesKindsByPolicy(podspec corev1.PodSpec) corev1.PodSpec { + if podspec.Volumes == nil || len(podspec.Volumes) == 0 { + return podspec + } + + // Remove unused volumes + for _, container := range podspec.Containers { + volMounts := []v1.VolumeMount{} + for _, vm := range container.VolumeMounts { + volume := getMatchingVolume(vm, podspec.Volumes) + if volume == (corev1.Volume{}) { + log.Warnf("Couldn't find a corresponding volume for volume mount %s", vm.Name) + continue + } + volMounts = append(volMounts, vm) + } + container.VolumeMounts = volMounts + } + + volumes := []v1.Volume{} + for _, v := range podspec.Volumes { + volumes = append(volumes, convertVolumeBySupportedKind(v, d.ClusterSpec)) + } + podspec.Volumes = volumes + + return podspec +} + +func getMatchingVolume(vm corev1.VolumeMount, vList []corev1.Volume) corev1.Volume { + for _, v := range vList { + if strings.Compare(v.Name, vm.Name) == 0 { + return v + } + } + return corev1.Volume{} +} diff --git a/internal/apiresource/imagestream.go b/internal/apiresource/imagestream.go new file mode 100644 index 000000000..7af297058 --- /dev/null +++ b/internal/apiresource/imagestream.go @@ -0,0 +1,114 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + "strings" + + okdimagev1 "github.com/openshift/api/image/v1" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +//TODO: Add support for Build, BuildConfig, ImageStreamImage, ImageStreamImport, ImageStreamLayers, ImageStreamMapping, ImageStreamTag, tekton + +const ( + // imageStreamKind defines the image stream kind + imageStreamKind = "ImageStream" +) + +// ImageStream handles all objects related to image stream +type ImageStream struct { + Cluster collecttypes.ClusterMetadataSpec +} + +// GetSupportedKinds returns kinds supported by ImageStream +func (d *ImageStream) GetSupportedKinds() []string { + return []string{imageStreamKind} +} + +// CreateNewResources converts IR to runtime objects +func (d *ImageStream) CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + + for _, service := range ir.Services { + if common.IsStringPresent(supportedKinds, imageStreamKind) { + newobjs := d.createImageStream(service.Name, service) + objs = append(objs, newobjs...) + } else { + log.Debugf("Could not find a valid resource type in cluster to create a ImageStream") + } + } + return objs +} + +// ConvertToClusterSupportedKinds converts kinds to cluster supported kinds +func (d *ImageStream) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if common.IsStringPresent(supportedKinds, imageStreamKind) { + if _, ok := obj.(*okdimagev1.ImageStream); ok { + return []runtime.Object{obj}, true + } + } + return nil, false +} + +// createImageStream creates a ImageStream object +func (d *ImageStream) createImageStream(name string, service irtypes.Service) []runtime.Object { + imageStreams := []runtime.Object{} + for _, serviceContainer := range service.Containers { + if serviceContainer.Image == "" { + serviceContainer.Image = name + } + + var tags []okdimagev1.TagReference + imagename, tag := common.GetImageNameAndTag(serviceContainer.Image) + tags = []okdimagev1.TagReference{ + { + From: &corev1.ObjectReference{ + Kind: "DockerImage", + Name: imagename, + }, + Name: tag, + }, + } + + dockerImageRepo := strings.Split(serviceContainer.Image, ":")[0] + + is := &okdimagev1.ImageStream{ + TypeMeta: metav1.TypeMeta{ + Kind: imageStreamKind, + APIVersion: okdimagev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: getServiceLabels(name), + }, + Spec: okdimagev1.ImageStreamSpec{ + DockerImageRepository: dockerImageRepo, + Tags: tags, + }, + } + imageStreams = append(imageStreams, is) + } + return imageStreams +} diff --git a/internal/apiresource/knativeservice.go b/internal/apiresource/knativeservice.go new file mode 100644 index 000000000..642e5f371 --- /dev/null +++ b/internal/apiresource/knativeservice.go @@ -0,0 +1,83 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + knativev1 "knative.dev/serving/pkg/apis/serving/v1" + + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +const ( + // knativeServiceKind defines the KNative service kind + knativeServiceKind string = "Service" +) + +// KnativeService handles the Knative service object +type KnativeService struct { + Cluster collecttypes.ClusterMetadataSpec +} + +// CreateNewResources creates new knative services for IR +func (d *KnativeService) CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + + for _, service := range ir.Services { + podSpec := service.PodSpec + podSpec.RestartPolicy = v1.RestartPolicyAlways + knativeservice := &knativev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: knativeServiceKind, + APIVersion: knativev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: service.Name, + Labels: getServiceLabels(service.Name), + Annotations: getAnnotations(service), + }, + Spec: knativev1.ServiceSpec{ + ConfigurationSpec: knativev1.ConfigurationSpec{ + Template: knativev1.RevisionTemplateSpec{ + Spec: knativev1.RevisionSpec{ + PodSpec: podSpec, + }, + }, + }, + }, + } + objs = append(objs, knativeservice) + } + return objs +} + +// ConvertToClusterSupportedKinds converts kinds to cluster supported kinds +func (d *KnativeService) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if d1, ok := obj.(*knativev1.Service); ok { + return []runtime.Object{d1}, true + } + return nil, false +} + +// GetSupportedKinds returns kinds supported by Knative service +func (d *KnativeService) GetSupportedKinds() []string { + return []string{knativeServiceKind} +} diff --git a/internal/apiresource/networkpolicy.go b/internal/apiresource/networkpolicy.go new file mode 100644 index 000000000..6ef3c0033 --- /dev/null +++ b/internal/apiresource/networkpolicy.go @@ -0,0 +1,113 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + log "github.com/sirupsen/logrus" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +const ( + networkPolicyKind = "NetworkPolicy" + networkSelector = "io." + types.AppName + ".network" + networkPresentLabel = "true" +) + +// NetworkPolicy handles NetworkPolicy objects +type NetworkPolicy struct { + Cluster collecttypes.ClusterMetadataSpec +} + +// GetSupportedKinds returns all kinds supported by the class +func (d *NetworkPolicy) GetSupportedKinds() []string { + return []string{networkPolicyKind} +} + +// CreateNewResources converts ir to runtime objects +func (d *NetworkPolicy) CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + + for _, service := range ir.Services { + // Create services depending on whether the service needs to be externally exposed + if common.IsStringPresent(supportedKinds, networkPolicyKind) { + for _, net := range service.Networks { + log.Debugf("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net) + obj, err := d.createNetworkPolicy(net) + if err != nil { + log.Warnf("Unable to create Network Policy for network %v for service %s : %s", net, service.Name, err) + } + objs = append(objs, obj) + } + } else { + log.Errorf("Could not find a valid resource type in cluster to create a NetworkPolicy") + } + } + return objs +} + +// ConvertToClusterSupportedKinds converts kinds to cluster supported kinds +func (d *NetworkPolicy) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if common.IsStringPresent(supportedKinds, networkPolicyKind) { + if _, ok := obj.(*networkingv1.NetworkPolicy); ok { + return []runtime.Object{obj}, true + } + } + return nil, false +} + +// CreateNetworkPolicy initializes Network policy +func (d *NetworkPolicy) createNetworkPolicy(networkName string) (*networkingv1.NetworkPolicy, error) { + + np := &networkingv1.NetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: networkPolicyKind, + APIVersion: networkingv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: networkName, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{networkSelector + "/" + networkName: networkPresentLabel}, + }, + Ingress: []networkingv1.NetworkPolicyIngressRule{{ + From: []networkingv1.NetworkPolicyPeer{{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: getNetworkPolicyLabels([]string{networkName}), + }, + }}, + }}, + }, + } + + return np, nil +} + +func getNetworkPolicyLabels(networks []string) map[string]string { + networklabels := make(map[string]string) + for _, network := range networks { + networklabels[networkSelector+"/"+network] = networkPresentLabel + } + return networklabels +} diff --git a/internal/apiresource/service.go b/internal/apiresource/service.go new file mode 100644 index 000000000..a2d91d70f --- /dev/null +++ b/internal/apiresource/service.go @@ -0,0 +1,460 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + "strconv" + "strings" + + okdroutev1 "github.com/openshift/api/route/v1" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +//TODO : Handle Endpoints + +const ( + serviceKind = "Service" + ingressKind = "Ingress" + routeKind = "Route" + // We are defaulting service port to 80 + defaultServicePort = 80 +) + +// Service handles all objects related to a service +type Service struct { + Cluster collecttypes.ClusterMetadataSpec +} + +// GetSupportedKinds returns supported kinds +func (d *Service) GetSupportedKinds() []string { + return []string{serviceKind, ingressKind, routeKind} +} + +// CreateNewResources converts IR to runtime objects +func (d *Service) CreateNewResources(ir irtypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + + for _, service := range ir.Services { + exposeobjectcreated := false + if service.ExposeService { + // Create services depending on whether the service needs to be externally exposed + if common.IsStringPresent(supportedKinds, routeKind) { + //Create Route + obj := d.createRoute(service) + objs = append(objs, obj) + exposeobjectcreated = true + } else if common.IsStringPresent(supportedKinds, ingressKind) { + //Create Ingress + obj := d.createIngress(service) + objs = append(objs, obj) + exposeobjectcreated = true + } + } + if common.IsStringPresent(supportedKinds, serviceKind) { + if exposeobjectcreated || !service.ExposeService { + //Create clusterip service + obj := d.createService(service, v1.ServiceTypeClusterIP) + objs = append(objs, obj) + } else { + //Create Nodeport service - TODO: Should it be load balancer or Nodeport? Should it be QA? + obj := d.createService(service, v1.ServiceTypeNodePort) + objs = append(objs, obj) + } + } else { + log.Errorf("Could not find a valid resource type in cluster to create a Service") + } + } + return objs +} + +// ConvertToClusterSupportedKinds converts kinds to cluster supported kinds +func (d *Service) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if common.IsStringPresent(supportedKinds, routeKind) { + if _, ok := obj.(*okdroutev1.Route); ok { + return []runtime.Object{obj}, true + } else if i, ok := obj.(*networkingv1beta1.Ingress); ok { + return d.ingressToRoute(*i), true + } else if s, ok := obj.(*v1.Service); ok { + if s.Spec.Type == v1.ServiceTypeLoadBalancer || s.Spec.Type == v1.ServiceTypeNodePort { + return d.serviceToRoute(*s), true + } + return []runtime.Object{obj}, true + } + } else if common.IsStringPresent(supportedKinds, ingressKind) { + if r, ok := obj.(*okdroutev1.Route); ok { + return d.routeToIngress(*r), true + } else if _, ok := obj.(*networkingv1beta1.Ingress); ok { + return []runtime.Object{obj}, true + } else if s, ok := obj.(*v1.Service); ok { + if s.Spec.Type == v1.ServiceTypeLoadBalancer || s.Spec.Type == v1.ServiceTypeNodePort { + return d.serviceToIngress(*s), true + } + return []runtime.Object{obj}, true + } + } else if common.IsStringPresent(supportedKinds, serviceKind) { + if r, ok := obj.(*okdroutev1.Route); ok { + return d.routeToService(*r), true + } else if i, ok := obj.(*networkingv1beta1.Ingress); ok { + return d.ingressToService(*i), true + } else if _, ok := obj.(*v1.Service); ok { + //TODO: Check if the destination cluster supports loadbalancer or nodeport and change between them. + return []runtime.Object{obj}, true + } + } + return nil, false +} + +func (d *Service) ingressToRoute(ingress networkingv1beta1.Ingress) []runtime.Object { + var weight int32 = 1 //Hard-coded to 1 to avoid Helm v3 errors + var ingressArray []okdroutev1.RouteIngress //Hard-coded to empty list to avoid Helm v3 errors + ingressArray = append(ingressArray, okdroutev1.RouteIngress{Host: ""}) + + objs := []runtime.Object{} + + for _, ingressspec := range ingress.Spec.Rules { + for _, path := range ingressspec.IngressRuleValue.HTTP.Paths { + route := &okdroutev1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: routeKind, + APIVersion: okdroutev1.SchemeGroupVersion.String(), + }, + ObjectMeta: ingress.ObjectMeta, + Spec: okdroutev1.RouteSpec{ + Port: &okdroutev1.RoutePort{ + TargetPort: path.Backend.ServicePort, + }, + To: okdroutev1.RouteTargetReference{ + Kind: serviceKind, + Name: path.Backend.ServiceName, + Weight: &weight, + }, + Host: ingressspec.Host, + }, + Status: okdroutev1.RouteStatus{ + Ingress: ingressArray, + }, + } + objs = append(objs, route) + } + } + + return objs +} + +func (d *Service) serviceToRoute(service v1.Service) []runtime.Object { + var weight int32 = 1 //Hard-coded to 1 to avoid Helm v3 errors + var ingressArray []okdroutev1.RouteIngress //Hard-coded to empty list to avoid Helm v3 errors + ingressArray = append(ingressArray, okdroutev1.RouteIngress{Host: ""}) + + objs := []runtime.Object{} + for _, serviceport := range service.Spec.Ports { + port := intstr.IntOrString{ + IntVal: serviceport.Port, + } + route := &okdroutev1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: routeKind, + APIVersion: okdroutev1.SchemeGroupVersion.String(), + }, + ObjectMeta: service.ObjectMeta, + Spec: okdroutev1.RouteSpec{ + Port: &okdroutev1.RoutePort{ + TargetPort: port, + }, + To: okdroutev1.RouteTargetReference{ + Kind: serviceKind, + Name: service.Name, + Weight: &weight, + }, + Host: "", + }, + Status: okdroutev1.RouteStatus{ + Ingress: ingressArray, + }, + } + objs = append(objs, route) + } + service.Spec.Type = v1.ServiceTypeClusterIP + objs = append(objs, &service) + + return objs +} + +func (d *Service) routeToIngress(route okdroutev1.Route) []runtime.Object { + ingress := &networkingv1beta1.Ingress{ + TypeMeta: metav1.TypeMeta{ + Kind: ingressKind, + APIVersion: networkingv1beta1.SchemeGroupVersion.String(), + }, + ObjectMeta: route.ObjectMeta, + Spec: networkingv1beta1.IngressSpec{ + Rules: []networkingv1beta1.IngressRule{ + { + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: "", + Backend: networkingv1beta1.IngressBackend{ + ServiceName: route.Spec.To.Name, + ServicePort: route.Spec.Port.TargetPort, + }, + }, + }, + }, + }, + Host: route.Spec.Host, + }, + }, + }, + } + + if strings.HasPrefix(route.Spec.Host, "https") { + ingress.Spec.TLS = []networkingv1beta1.IngressTLS{ + { + Hosts: []string{route.Spec.Host}, + SecretName: "tlssecret-replaceme", + }, + } + } + + return []runtime.Object{ingress} +} + +func (d *Service) serviceToIngress(service v1.Service) []runtime.Object { + rules := []networkingv1beta1.IngressRule{} + for _, serviceport := range service.Spec.Ports { + port := intstr.IntOrString{ + IntVal: serviceport.Port, + } + rule := networkingv1beta1.IngressRule{ + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: "", + Backend: networkingv1beta1.IngressBackend{ + ServiceName: service.Name, + ServicePort: port, + }, + }, + }, + }, + }, + Host: "", + } + rules = append(rules, rule) + } + ingress := &networkingv1beta1.Ingress{ + TypeMeta: metav1.TypeMeta{ + Kind: ingressKind, + APIVersion: networkingv1beta1.SchemeGroupVersion.String(), + }, + ObjectMeta: service.ObjectMeta, + Spec: networkingv1beta1.IngressSpec{ + Rules: rules, + }, + } + service.Spec.Type = v1.ServiceTypeClusterIP + return []runtime.Object{ingress, &service} +} + +func (d *Service) routeToService(route okdroutev1.Route) []runtime.Object { + //TODO: Think through how will the clusterip service that was originally there will behave when merged with this service? + svc := &v1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: serviceKind, + APIVersion: v1.SchemeGroupVersion.String(), + }, + ObjectMeta: route.ObjectMeta, + Spec: v1.ServiceSpec{ + //TODO: How to choose between nodeport and loadbalancer? + Type: v1.ServiceTypeNodePort, + Ports: []v1.ServicePort{ + { + Name: route.Spec.Port.TargetPort.StrVal, + Port: route.Spec.Port.TargetPort.IntVal, + }, + }, + }, + } + svc.Name = route.Spec.To.Name + + return []runtime.Object{svc} +} + +func (d *Service) ingressToService(ingress networkingv1beta1.Ingress) []runtime.Object { + objs := []runtime.Object{} + for _, ingressspec := range ingress.Spec.Rules { + for _, path := range ingressspec.IngressRuleValue.HTTP.Paths { + svc := &v1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: serviceKind, + APIVersion: v1.SchemeGroupVersion.String(), + }, + ObjectMeta: ingress.ObjectMeta, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeNodePort, + Ports: []v1.ServicePort{ + { + //TODO: Check if this is the right mapping + Name: path.Backend.ServicePort.StrVal, + Port: path.Backend.ServicePort.IntVal, + }, + }, + }, + } + svc.Name = path.Backend.ServiceName + objs = append(objs, svc) + } + } + return objs +} + +//TODO: Remove these two sections after helm v3 issue is fixed +//[https://github.com/openshift/origin/issues/24060] +//[https://bugzilla.redhat.com/show_bug.cgi?id=1773682] +func (d *Service) createRoute(service irtypes.Service) *okdroutev1.Route { + var weight int32 = 1 //Hard-coded to 1 to avoid Helm v3 errors + var ingressArray []okdroutev1.RouteIngress //Hard-coded to empty list to avoid Helm v3 errors + ingressArray = append(ingressArray, okdroutev1.RouteIngress{Host: ""}) + + route := &okdroutev1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: routeKind, + APIVersion: okdroutev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: service.Name, + Labels: getServiceLabels(service.Name), + }, + Spec: okdroutev1.RouteSpec{ + Port: &okdroutev1.RoutePort{ + TargetPort: intstr.IntOrString{ + IntVal: defaultServicePort, + }, + }, + To: okdroutev1.RouteTargetReference{ + Kind: serviceKind, + Name: service.Name, + Weight: &weight, + }, + }, + Status: okdroutev1.RouteStatus{ + Ingress: ingressArray, + }, + } + return route +} + +func (d *Service) createIngress(service irtypes.Service) *networkingv1beta1.Ingress { + ingress := &networkingv1beta1.Ingress{ + TypeMeta: metav1.TypeMeta{ + Kind: ingressKind, + APIVersion: networkingv1beta1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: service.Name, + Labels: getServiceLabels(service.Name), + }, + Spec: networkingv1beta1.IngressSpec{ + Rules: []networkingv1beta1.IngressRule{ + { + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1beta1.IngressBackend{ + ServiceName: service.Name, + ServicePort: intstr.IntOrString{ + IntVal: defaultServicePort, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + return ingress +} + +// createService creates a service +func (d *Service) createService(service irtypes.Service, serviceType v1.ServiceType) *v1.Service { + ports := d.getServicePorts(service, serviceType) + headless := false + if len(ports) == 0 { + // Configure a dummy port: https://github.com/kubernetes/kubernetes/issues/32766. + ports = []v1.ServicePort{{ + Name: "headless", + Port: 55555, + }} + headless = true + } + svc := &v1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: serviceKind, + APIVersion: v1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: service.Name, + Labels: getServiceLabels(service.Name), + Annotations: getAnnotations(service), + }, + Spec: v1.ServiceSpec{ + Type: serviceType, + Selector: getServiceLabels(service.Name), + Ports: ports, + }, + } + if headless { + svc.Spec.ClusterIP = "None" + } + return svc +} + +// GetServicePorts configure the container service ports. +func (d *Service) getServicePorts(service irtypes.Service, serviceType v1.ServiceType) []v1.ServicePort { + servicePorts := []v1.ServicePort{} + for _, serviceContainer := range service.Containers { + for _, port := range serviceContainer.Ports { + var servicePort v1.ServicePort + var targetPort intstr.IntOrString + targetPort.IntVal = port.ContainerPort + targetPort.StrVal = strconv.Itoa(int(targetPort.IntVal)) + servicePort = v1.ServicePort{ + Name: strconv.Itoa(defaultServicePort), + Port: defaultServicePort, + TargetPort: targetPort, + } + servicePorts = append(servicePorts, servicePort) + } + } + return servicePorts +} diff --git a/internal/apiresource/storage.go b/internal/apiresource/storage.go new file mode 100644 index 000000000..dcf52d090 --- /dev/null +++ b/internal/apiresource/storage.go @@ -0,0 +1,292 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresource + +import ( + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + common "github.com/konveyor/move2kube/internal/common" + internaltypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +// Storage handles all storage objectss +type Storage struct { + Cluster collecttypes.ClusterMetadataSpec +} + +// GetSupportedKinds returns cluster supported kinds +func (s *Storage) GetSupportedKinds() []string { + return []string{string(internaltypes.PVCKind), string(internaltypes.ConfigMapKind), string(internaltypes.SecretKind)} +} + +// CreateNewResources converts IR objects to runtime objects +func (s *Storage) CreateNewResources(ir internaltypes.IR, supportedKinds []string) []runtime.Object { + objs := []runtime.Object{} + + for _, stObj := range ir.Storages { + if stObj.StorageType == internaltypes.ConfigMapKind { + if !common.IsStringPresent(supportedKinds, string(internaltypes.ConfigMapKind)) && common.IsStringPresent(supportedKinds, string(internaltypes.SecretKind)) { + objs = append(objs, s.createSecret(stObj)) + } else { + objs = append(objs, s.createConfigMap(stObj)) + } + } + + if stObj.StorageType == internaltypes.SecretKind { + if !common.IsStringPresent(supportedKinds, string(internaltypes.SecretKind)) && common.IsStringPresent(supportedKinds, string(internaltypes.ConfigMapKind)) { + objs = append(objs, s.createConfigMap(stObj)) + } else { + objs = append(objs, s.createSecret(stObj)) + } + } + + if stObj.StorageType == internaltypes.PullSecretKind { + objs = append(objs, s.createSecret(stObj)) + } + + if stObj.StorageType == internaltypes.PVCKind { + objs = append(objs, s.createPVC(stObj)) + } + } + + return objs +} + +// ConvertToClusterSupportedKinds converts kinds to cluster supported kinds +func (s *Storage) ConvertToClusterSupportedKinds(obj runtime.Object, supportedKinds []string, otherobjs []runtime.Object) ([]runtime.Object, bool) { + if cfgMap, ok := obj.(*corev1.ConfigMap); ok { + if !common.IsStringPresent(supportedKinds, string(internaltypes.ConfigMapKind)) && common.IsStringPresent(supportedKinds, string(internaltypes.SecretKind)) { + return []runtime.Object{convertCfgMapToSecret(*cfgMap)}, true + } + return []runtime.Object{cfgMap}, true + } + + if secret, ok := obj.(*corev1.Secret); ok { + if !common.IsStringPresent(supportedKinds, string(internaltypes.SecretKind)) && common.IsStringPresent(supportedKinds, string(internaltypes.ConfigMapKind)) { + return []runtime.Object{convertSecretToCfgMap(*secret)}, true + } + return []runtime.Object{secret}, true + } + + if pvc, ok := obj.(*corev1.PersistentVolumeClaim); ok { + if !common.IsStringPresent(supportedKinds, string(internaltypes.PVCKind)) { + log.Warnf("PVC not supported in target cluster. [%s]", pvc.Name) + } + return []runtime.Object{pvc}, true + } + return nil, false +} + +func (s *Storage) createConfigMap(st internaltypes.Storage) *corev1.ConfigMap { + cmName := common.MakeFileNameCompliant(st.Name) + + data := make(map[string]string) + for k, v := range st.Content { + data[k] = string(v) + } + + configMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: string(internaltypes.ConfigMapKind), + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmName, + }, + Data: data, + } + return configMap +} + +func (s *Storage) createSecret(st internaltypes.Storage) *corev1.Secret { + secretName := common.MakeFileNameCompliant(st.Name) + secType := corev1.SecretTypeOpaque + if st.StorageType == internaltypes.PullSecretKind { + secType = corev1.SecretTypeDockerConfigJson + } + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: string(internaltypes.SecretKind), + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Type: secType, + Data: st.Content, + } + return secret +} + +func (s *Storage) createPVC(st internaltypes.Storage) *corev1.PersistentVolumeClaim { + pvc := &corev1.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + Kind: string(internaltypes.PVCKind), + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: st.Name, + }, + Spec: st.PersistentVolumeClaimSpec, + } + + log.Debugf("%+v", st.PersistentVolumeClaimSpec) + return pvc +} + +func convertCfgMapToSecret(cfgMap corev1.ConfigMap) *corev1.Secret { + + secretDataMap := stringMapToByteMap(cfgMap.Data) + + s := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: string(internaltypes.SecretKind), + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cfgMap.Name, + Labels: cfgMap.Labels, + }, + Type: corev1.SecretTypeOpaque, + Data: secretDataMap, + } + + return s +} + +func convertSecretToCfgMap(s corev1.Secret) *corev1.ConfigMap { + cmDataMap := byteMapToStringMap(s.Data) + + cm := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: string(internaltypes.ConfigMapKind), + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + Labels: s.Labels, + }, + Data: cmDataMap, + } + + return cm +} + +func convertPVCVolumeToEmptyVolume(vPVC corev1.Volume) *corev1.Volume { + vEmptySrc := &corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + } + + return &corev1.Volume{ + Name: vPVC.Name, + VolumeSource: *vEmptySrc, + } +} + +func convertCfgMapVolumeToSecretVolume(vCfgMap corev1.Volume) *corev1.Volume { + vSecretVolSrc := corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: vCfgMap.ConfigMap.Name, + Items: vCfgMap.ConfigMap.Items, + DefaultMode: vCfgMap.ConfigMap.DefaultMode, + }, + } + + v := &corev1.Volume{ + Name: vCfgMap.Name, + VolumeSource: vSecretVolSrc, + } + return v +} + +func convertSecretVolumeToCfgMapVolume(vs corev1.Volume) *corev1.Volume { + vSrc := &corev1.ConfigMapVolumeSource{} + vSrc.Name = vs.Secret.SecretName + vSrc.Items = vs.Secret.Items + vSrc.DefaultMode = vs.Secret.DefaultMode + + vCMVolSrc := corev1.VolumeSource{ + ConfigMap: vSrc, + } + + v := &corev1.Volume{ + Name: vs.Secret.SecretName, + VolumeSource: vCMVolSrc, + } + + return v +} + +func convertVolumeBySupportedKind(volume corev1.Volume, cluster collecttypes.ClusterMetadataSpec) (nvolume corev1.Volume) { + + if volume == (corev1.Volume{}) { + return corev1.Volume{} + } + + if volume.VolumeSource.ConfigMap != nil { + if cluster.GetSupportedVersions(string(internaltypes.ConfigMapKind)) == nil && cluster.GetSupportedVersions(string(internaltypes.SecretKind)) != nil { + return *convertCfgMapVolumeToSecretVolume(volume) + } + return volume + } + if volume.VolumeSource.Secret != nil { + if cluster.GetSupportedVersions(string(internaltypes.SecretKind)) == nil && cluster.GetSupportedVersions(string(internaltypes.ConfigMapKind)) != nil { + return *convertSecretVolumeToCfgMapVolume(volume) + } + return volume + } + if volume.VolumeSource.PersistentVolumeClaim != nil { + //PVC -> Empty (If PVC not available) + if cluster.GetSupportedVersions(string(internaltypes.PVCKind)) == nil { + vEmpty := convertPVCVolumeToEmptyVolume(volume) + log.Warnf("PVC not supported in target cluster. Defaulting volume [%s] to emptyDir", volume.Name) + return *vEmpty + + } + return volume + } + if volume.VolumeSource.HostPath != nil || volume.VolumeSource.EmptyDir != nil { + return volume + } + log.Warnf("Unsupported storage type (volume) detected") + + return corev1.Volume{} +} + +func stringMapToByteMap(sm map[string]string) map[string][]byte { + bm := make(map[string][]byte) + + for k, v := range sm { + bm[k] = []byte(v) + } + + return bm +} + +func byteMapToStringMap(bm map[string][]byte) map[string]string { + sm := make(map[string]string) + + for k, v := range bm { + sm[k] = string(v) + } + + return sm +} diff --git a/internal/apiresourceset/apiresourceset.go b/internal/apiresourceset/apiresourceset.go new file mode 100644 index 000000000..e2b4985d9 --- /dev/null +++ b/internal/apiresourceset/apiresourceset.go @@ -0,0 +1,38 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresourceset + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func intersection(objs1 []runtime.Object, objs2 []runtime.Object) []runtime.Object { + objs := []runtime.Object{} + for _, obj1 := range objs1 { + found := false + for _, obj2 := range objs2 { + if obj1 == obj2 { + found = true + break + } + } + if found { + objs = append(objs, obj1) + } + } + return objs +} diff --git a/internal/apiresourceset/k8sapiresourceset.go b/internal/apiresourceset/k8sapiresourceset.go new file mode 100644 index 000000000..1e3d92177 --- /dev/null +++ b/internal/apiresourceset/k8sapiresourceset.go @@ -0,0 +1,143 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresourceset + +import ( + "io/ioutil" + + log "github.com/sirupsen/logrus" + + okdapi "github.com/openshift/api" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + k8sapischeme "k8s.io/client-go/kubernetes/scheme" + + "github.com/konveyor/move2kube/internal/apiresource" + "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + tektonscheme "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme" +) + +// K8sAPIResourceSet for handling K8s related resources +type K8sAPIResourceSet struct { +} + +// GetScheme returns K8s scheme +func (k *K8sAPIResourceSet) GetScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + _ = okdapi.Install(scheme) + _ = okdapi.InstallKube(scheme) + _ = k8sapischeme.AddToScheme(scheme) + _ = tektonscheme.AddToScheme(scheme) + return scheme +} + +func (k *K8sAPIResourceSet) getAPIResources(ir irtypes.IR) []apiresource.APIResource { + apiresources := []apiresource.APIResource{{IAPIResource: &apiresource.Deployment{ClusterSpec: ir.TargetClusterSpec}}, {IAPIResource: &apiresource.Storage{Cluster: ir.TargetClusterSpec}}, {IAPIResource: &apiresource.Service{Cluster: ir.TargetClusterSpec}}, {IAPIResource: &apiresource.ImageStream{Cluster: ir.TargetClusterSpec}}, {IAPIResource: &apiresource.NetworkPolicy{Cluster: ir.TargetClusterSpec}}} + return apiresources +} + +// CreateAPIResources converts IR to runtime objects +func (k *K8sAPIResourceSet) CreateAPIResources(ir irtypes.IR) []runtime.Object { + targetobjs := []runtime.Object{} + ignoredobjs := ir.CachedObjects + for _, a := range k.getAPIResources(ir) { + a.SetClusterContext(ir.TargetClusterSpec) + resourceignoredobjs := a.LoadResources(ir.CachedObjects) + ignoredobjs = intersection(ignoredobjs, resourceignoredobjs) + objs := a.GetUpdatedResources(ir) + targetobjs = append(targetobjs, objs...) + } + targetobjs = append(targetobjs, ignoredobjs...) + return targetobjs +} + +// GetServiceOptions analyses a directory and returns possible plan services +func (k *K8sAPIResourceSet) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + services := make([]plantypes.Service, 0) + //TODO: Should we add service analysis too, to get service name? + + codecs := serializer.NewCodecFactory(k.GetScheme()) + + files, err := common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize k8 yamls : %s", err) + } + for _, path := range files { + data, err := ioutil.ReadFile(path) + if err != nil { + log.Debugf("ignoring file %s", path) + continue + } + obj, _, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", path) + continue + } else { + name, _, err := (&apiresource.Deployment{}).GetNameAndPodSpec(obj) + if err == nil { + service := newK8sService(name) + relpath, _ := plan.GetRelativePath(path) + service.SourceArtifacts[plantypes.K8sFileArtifactType] = []string{relpath} + services = append(services, service) + } + } + } + return services, nil +} + +// Translate tanslates plan services to IR +func (k *K8sAPIResourceSet) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + ir.Services = make(map[string]irtypes.Service) + codecs := serializer.NewCodecFactory(k.GetScheme()) + + for _, service := range services { + irservice := irtypes.Service{Name: service.ServiceName} + if len(service.SourceArtifacts[plantypes.K8sFileArtifactType]) > 0 { + fullpath := p.GetFullPath(service.SourceArtifacts[plantypes.K8sFileArtifactType][0]) + data, err := ioutil.ReadFile(fullpath) + if err != nil { + log.Debugf("Unable to load file : %s", fullpath) + continue + } + obj, _, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", fullpath) + continue + } + _, podSpec, err := (&apiresource.Deployment{}).GetNameAndPodSpec(obj) + if err == nil { + irservice.PodSpec = podSpec + } + } else { + log.Warnf("No k8s artifacts found in service %s", service.ServiceName) + } + ir.Services[service.ServiceName] = irservice + } + return ir, nil +} + +func newK8sService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, plantypes.Kube2KubeTranslation) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + service.SourceTypes = []plantypes.SourceTypeValue{plantypes.K8sSourceTypeValue} + service.UpdateContainerBuildPipeline = false + service.UpdateDeployPipeline = true + return service +} diff --git a/internal/apiresourceset/knativeapiresourceset.go b/internal/apiresourceset/knativeapiresourceset.go new file mode 100644 index 000000000..c4d379b07 --- /dev/null +++ b/internal/apiresourceset/knativeapiresourceset.go @@ -0,0 +1,137 @@ +/* +Copyright IBM Corporation 2020 + +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 apiresourceset + +import ( + "io/ioutil" + + log "github.com/sirupsen/logrus" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + knativev1 "knative.dev/serving/pkg/apis/serving/v1" + knative "knative.dev/serving/pkg/client/clientset/versioned/scheme" + + "github.com/konveyor/move2kube/internal/apiresource" + "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// KnativeAPIResourceSet manages knative related objects +type KnativeAPIResourceSet struct { +} + +// GetScheme returns knative scheme object +func (k *KnativeAPIResourceSet) GetScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + _ = knative.AddToScheme(scheme) + return scheme +} + +func (k *KnativeAPIResourceSet) getAPIResources(ir irtypes.IR) []apiresource.APIResource { + apiresources := []apiresource.APIResource{{IAPIResource: &apiresource.KnativeService{Cluster: ir.TargetClusterSpec}}} + return apiresources +} + +// CreateAPIResources converts ir object to runtime objects +func (k *KnativeAPIResourceSet) CreateAPIResources(ir irtypes.IR) []runtime.Object { + targetobjs := []runtime.Object{} + ignoredobjs := ir.CachedObjects + for _, apiresource := range k.getAPIResources(ir) { + apiresource.SetClusterContext(ir.TargetClusterSpec) + resourceignoredobjs := apiresource.LoadResources(ir.CachedObjects) + ignoredobjs = intersection(ignoredobjs, resourceignoredobjs) + objs := apiresource.GetUpdatedResources(ir) + targetobjs = append(targetobjs, objs...) + } + targetobjs = append(targetobjs, ignoredobjs...) + return targetobjs +} + +// GetServiceOptions returns plan service options for an input folder +func (k *KnativeAPIResourceSet) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + services := make([]plantypes.Service, 0) + + codecs := serializer.NewCodecFactory(k.GetScheme()) + + files, err := common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize knative yamls : %s", err) + } + for _, path := range files { + //relpath, _ := plan.GetRelativePath(path) + data, err := ioutil.ReadFile(path) + if err != nil { + log.Debugf("ignoring file %s", path) + continue + } + obj, _, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", path) + continue + } else { + if d1, ok := obj.(*knativev1.Service); ok { + service := newKnativeService(d1.Name) + relpath, _ := plan.GetRelativePath(path) + service.SourceArtifacts[plantypes.KnativeFileArtifactType] = []string{relpath} + services = append(services, service) + } + } + } + return services, nil +} + +// Translate translates plan services to IR +func (k *KnativeAPIResourceSet) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + ir.Services = make(map[string]irtypes.Service) + codecs := serializer.NewCodecFactory(k.GetScheme()) + + for _, service := range services { + irservice := irtypes.Service{Name: service.ServiceName} + if len(service.SourceArtifacts[plantypes.KnativeFileArtifactType]) > 0 { + fullpath := p.GetFullPath(service.SourceArtifacts[plantypes.KnativeFileArtifactType][0]) + data, err := ioutil.ReadFile(fullpath) + if err != nil { + log.Debugf("Unable to load file : %s", fullpath) + continue + } + obj, _, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", fullpath) + continue + } + if d1, ok := obj.(*knativev1.Service); ok { + irservice.PodSpec = d1.Spec.ConfigurationSpec.Template.Spec.PodSpec + } + } else { + log.Warnf("No knative artifacts found in service %s", service.ServiceName) + } + ir.Services[service.ServiceName] = irservice + } + return ir, nil +} + +func newKnativeService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, plantypes.Knative2KubeTranslation) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + service.SourceTypes = []plantypes.SourceTypeValue{plantypes.KNativeSourceTypeValue} + service.UpdateContainerBuildPipeline = false + service.UpdateDeployPipeline = true + return service +} diff --git a/internal/assets/assets.go b/internal/assets/assets.go new file mode 100644 index 000000000..c25273aee --- /dev/null +++ b/internal/assets/assets.go @@ -0,0 +1,19 @@ +/* +Copyright IBM Corporation 2020 + +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 assets + +//go:generate go run github.com/konveyor/move2kube/internal/common/generator . maketar diff --git a/internal/assets/constants.go b/internal/assets/constants.go new file mode 100644 index 000000000..71b6f83fe --- /dev/null +++ b/internal/assets/constants.go @@ -0,0 +1,23 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2020-09-14 15:52:43.971455 +0530 IST m=+0.005163665 + +/* +Copyright IBM Corporation 2020 + +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 assets + +const Tar = `LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAwMDAwADEzNzI3NjMyNDQ0ADAxNDY1NwAgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkb2NrZXJmaWxlcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDc1NQAwMDAwNzY1ADAwMDAwMjQAMDAwMDAwMDAwMDAAMTM3Mjc2MzI0NDQAMDE3MDE0ACA1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHVzdGFyADAwaGFyaWtyaXNobmFuYmFsYWdvcGFsAAAAAAAAAAAAAABzdGFmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAAMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRvY2tlcmZpbGVzL2RqYW5nbwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNzU1ADAwMDA3NjUAMDAwMDAyNAAwMDAwMDAwMDAwMAAxMzcyNzYzMjQ0NAAwMjAyNTYAIDUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBoYXJpa3Jpc2huYW5iYWxhZ29wYWwAAAAAAAAAAAAAAHN0YWZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvZGphbmdvL0RvY2tlcmZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2NDQAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAxNDE0ADEzNzI3NjMyNDQ0ADAyMjMyNwAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjICAgQ29weXJpZ2h0IElCTSBDb3Jwb3JhdGlvbiAyMDIwCiMKIyAgIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwojICAgeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgojICAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyAgIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIyAgIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgICBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyAgIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIyAgIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKRlJPTSByZWdpc3RyeS5hY2Nlc3MucmVkaGF0LmNvbS91Ymk4L3B5dGhvbi0zNgoKV09SS0RJUiAvYXBwCkNPUFkgLiAuClJVTiBwaXAgaW5zdGFsbCAtciByZXF1aXJlbWVudHMudHh0CkVYUE9TRSB7ey5Qb3J0fX0KQ01EIFsicHl0aG9uIiwgIm1hbmFnZS5weSIsICJydW5zZXJ2ZXIiLCAie3suQklORElOR319Il0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvZGphbmdvL20ya2RmZGV0ZWN0LnNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAxNDIwADEzNzI3NjMyNDQ0ADAyMzA2NQAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjICAgQ29weXJpZ2h0IElCTSBDb3Jwb3JhdGlvbiAyMDIwCiMKIyAgIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwojICAgeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgojICAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyAgIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIyAgIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgICBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyAgIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIyAgIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKIyBUYWtlcyBhcyBpbnB1dCB0aGUgc291cmNlIGZvbGRlciBhbmQgcmV0dXJucyBlcnJvciBpZiBpdCBpcyBub3QgZml0CkJBU0VfRElSPSQxCgppZiBbICEgLWYgIiQxL1BpcGZpbGUiIF07IHRoZW4KICAgZXhpdCAxCmVsc2UKICAgZWNobyAneyJQb3J0IjogODA4MCwgIkJJTkRJTkciOiAiMC4wLjAuMDo4MDgwIn0nCmZpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvZ29sYW5nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAwMDAwADEzNzI3NjMyNDQ0ADAyMDI2MwAgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkb2NrZXJmaWxlcy9nb2xhbmcvRG9ja2VyZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDY0NAAwMDAwNzY1ADAwMDAwMjQAMDAwMDAwMDIzNjUAMTM3Mjc2MzI0NDQAMDIyMzQyACAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHVzdGFyADAwaGFyaWtyaXNobmFuYmFsYWdvcGFsAAAAAAAAAAAAAABzdGFmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAAMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMgICBDb3B5cmlnaHQgSUJNIENvcnBvcmF0aW9uIDIwMjAKIwojICAgTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiMgICB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgICBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKIwojICAgICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKIwojICAgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojICAgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyAgIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgojICAgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojICAgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgojIEJ1aWxkIEFwcApGUk9NIHJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL3ViaTgvdWJpOmxhdGVzdCBBUyBidWlsZF9iYXNlCgpXT1JLRElSIC90ZW1wCkVOViBHT1BBVEg9L2dvCkVOViBQQVRIPSRHT1BBVEgvYmluOi91c3IvbG9jYWwvZ28vYmluOi91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbgpSVU4gY3VybCAtbyBnby50YXIuZ3ogaHR0cHM6Ly9kbC5nb29nbGUuY29tL2dvL2dvMS4xNS5saW51eC1hbWQ2NC50YXIuZ3oKUlVOIHRhciAteHpmIGdvLnRhci5neiAmJiBtdiBnbyAvdXNyL2xvY2FsLwpSVU4geXVtIGluc3RhbGwgZ2l0IG1ha2UgLXkgClJVTiBta2RpciAtcCAkR09QQVRIL3NyYyAkR09QQVRIL2JpbiAmJiBjaG1vZCAtUiA3NzcgJEdPUEFUSApXT1JLRElSIC97ey5BUFBOQU1FfX0KQ09QWSAuIC4KUlVOIGdvIGJ1aWxkIC1vIHt7LkFQUE5BTUV9fQpSVU4gY3AgLi97ey5BUFBOQU1FfX0gL2Jpbi97ey5BUFBOQU1FfX0KCiMgUnVuIEFwcApGUk9NIHJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL3ViaTgvdWJpOmxhdGVzdApDT1BZIC0tZnJvbT1idWlsZF9iYXNlIC9iaW4ve3suQVBQTkFNRX19IC9iaW4ve3suQVBQTkFNRX19CgpFWFBPU0Uge3suUG9ydH19CgpDTUQgWyJ7ey5BUFBOQU1FfX0iXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRvY2tlcmZpbGVzL2dvbGFuZy9tMmtkZmRldGVjdC5zaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNzU1ADAwMDA3NjUAMDAwMDAyNAAwMDAwMDAwMTQ2NwAxMzcyNzYzMjQ0NAAwMjMxMDUAIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBoYXJpa3Jpc2huYW5iYWxhZ29wYWwAAAAAAAAAAAAAAHN0YWZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIyAgIENvcHlyaWdodCBJQk0gQ29ycG9yYXRpb24gMjAyMAojCiMgICBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyAgIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIyAgIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAojCiMgICAgICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgICBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgICBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAojICAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgICBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgICBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCiMgVGFrZXMgYXMgaW5wdXQgdGhlIHNvdXJjZSBkaXJlY3RvcnkgYW5kIHJldHVybnMgZXJyb3IgaWYgaXQgaXMgbm90IGZpdApmb3VuZD1gZmluZCAkMS8uIC1uYW1lICIqLmdvIiAtcHJpbnQgLXF1aXQgfCB3YyAtbGAKCmlmIFsgISAkZm91bmQgLWVxIDEgXTsgdGhlbgogICAgZXhpdCAxCmVsc2UKICAgIGVjaG8gJ3siUG9ydCI6IDgwODAsICJBUFBOQU1FIjogImFwcC1iaW4ifScKZmkgCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRvY2tlcmZpbGVzL2phdmFhbnQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNzU1ADAwMDA3NjUAMDAwMDAyNAAwMDAwMDAwMDAwMAAxMzcyNzYzMjQ0NAAwMjA0NDAAIDUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBoYXJpa3Jpc2huYW5iYWxhZ29wYWwAAAAAAAAAAAAAAHN0YWZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvamF2YWFudC9Eb2NrZXJmaWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2NDQAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAyMjY3ADEzNzI3NjMyNDQ0ADAyMjUyMAAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjICAgQ29weXJpZ2h0IElCTSBDb3Jwb3JhdGlvbiAyMDIwCiMKIyAgIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwojICAgeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgojICAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyAgIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIyAgIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgICBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyAgIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIyAgIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKRlJPTSByZWdpc3RyeS5hY2Nlc3MucmVkaGF0LmNvbS91Ymk4L3ViaTpsYXRlc3QgQVMgYnVpbGRfYmFzZQpSVU4geXVtIGluc3RhbGwgLXkgamF2YS0xLjguMC1vcGVuamRrLWRldmVsClJVTiB5dW0gaW5zdGFsbCAteSB3Z2V0ClJVTiB5dW0gaW5zdGFsbCAteSB1bnppcApSVU4gd2dldCBodHRwczovL21pcnJvcnMuZXN0b2ludGVybmV0LmluL2FwYWNoZS8vYW50L2JpbmFyaWVzL2FwYWNoZS1hbnQtMS4xMC44LWJpbi56aXAgLVAgL3RtcApSVU4gdW56aXAgLWQgL29wdC9hcGFjaGUtYW50IC90bXAvYXBhY2hlLWFudC0xLjEwLjgtYmluLnppcApFTlYgUEFUSD0iJHtQQVRIfTovb3B0L2FwYWNoZS1hbnQvYXBhY2hlLWFudC0xLjEwLjgvYmluLyIKQ09QWSAuIC97ey5BUFBOQU1FfX0KV09SS0RJUiAve3suQVBQTkFNRX19ClJVTiB7ey5BTlRDTUR9fQoKRlJPTSByZWdpc3RyeS5hY2Nlc3MucmVkaGF0LmNvbS9qYm9zcy1lYXAtNi9lYXA2NC1vcGVuc2hpZnQ6bGF0ZXN0CkVYUE9TRSB7ey5Qb3J0fX0KQ09QWSAtLWZyb209YnVpbGRfYmFzZSAve3suQVBQTkFNRX19L291dHB1dC97ey5BUFBOQU1FfX0uZWFyIC9vcHQvZWFwL3N0YW5kYWxvbmUvZGVwbG95bWVudHMvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkb2NrZXJmaWxlcy9qYXZhYW50L20ya2RmZGV0ZWN0LnNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDc1NQAwMDAwNzY1ADAwMDAwMjQAMDAwMDAwMDE0MzUAMTM3Mjc2MzI0NDQAMDIzMjU1ACAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHVzdGFyADAwaGFyaWtyaXNobmFuYmFsYWdvcGFsAAAAAAAAAAAAAABzdGFmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAAMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMgICBDb3B5cmlnaHQgSUJNIENvcnBvcmF0aW9uIDIwMjAKIwojICAgTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiMgICB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgICBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKIwojICAgICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKIwojICAgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojICAgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyAgIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgojICAgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojICAgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgojIFRha2VzIGFzIGlucHV0IHRoZSBzb3VyY2UgZGlyZWN0b3J5IGFuZCByZXR1cm5zIGVycm9yIGlmIGl0IGlzIG5vdCBmaXQKaWYgWyAhIC1mICIkMS9idWlsZC54bWwiIF07IHRoZW4KICAgZXhpdCAxCmVsc2UKICAgZWNobyAneyJQb3J0IjogODA4MCwgIkFOVENNRCI6ICJhbnQgYWxsIiwgIkFQUE5BTUUiOiAic2ltcGxld2ViYXBwIn0nCmZpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkb2NrZXJmaWxlcy9qYXZhZ3JhZGxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDc1NQAwMDAwNzY1ADAwMDAwMjQAMDAwMDAwMDAwMDAAMTM3Mjc2MzI0NDQAMDIxMTE0ACA1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHVzdGFyADAwaGFyaWtyaXNobmFuYmFsYWdvcGFsAAAAAAAAAAAAAABzdGFmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAAMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRvY2tlcmZpbGVzL2phdmFncmFkbGUvRG9ja2VyZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNjQ0ADAwMDA3NjUAMDAwMDAyNAAwMDAwMDAwMjIzMwAxMzcyNzYzMjQ0NAAwMjMxNjUAIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBoYXJpa3Jpc2huYW5iYWxhZ29wYWwAAAAAAAAAAAAAAHN0YWZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIyAgIENvcHlyaWdodCBJQk0gQ29ycG9yYXRpb24gMjAyMAojCiMgICBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyAgIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIyAgIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAojCiMgICAgICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgICBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgICBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAojICAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgICBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgICBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCiMgQnVpbGQgQXBwCkZST00gcmVnaXN0cnkuYWNjZXNzLnJlZGhhdC5jb20vdWJpOC91Ymk6bGF0ZXN0IEFTIGJ1aWxkX2Jhc2UKUlVOIHl1bSBpbnN0YWxsIC15IGphdmEtMS44LjAtb3Blbmpkay1kZXZlbApSVU4geXVtIGluc3RhbGwgLXkgd2dldApSVU4geXVtIGluc3RhbGwgLXkgdW56aXAKUlVOIHdnZXQgaHR0cHM6Ly9zZXJ2aWNlcy5ncmFkbGUub3JnL2Rpc3RyaWJ1dGlvbnMvZ3JhZGxlLTYuNi1iaW4uemlwIC1QIC90bXAKUlVOIHVuemlwIC1kIC9vcHQvZ3JhZGxlIC90bXAvZ3JhZGxlLTYuNi1iaW4uemlwCkVOViBQQVRIPSIke1BBVEh9Oi9vcHQvZ3JhZGxlL2dyYWRsZS02LjYvYmluLyIKQ09QWSAuIC97ey5BUFBOQU1FfX0KV09SS0RJUiAve3suQVBQTkFNRX19ClJVTiBncmFkbGUgYnVpbGQKCiMgUnVuIEFwcApGUk9NIHJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL2pib3NzLWVhcC02L2VhcDY0LW9wZW5zaGlmdDpsYXRlc3QKRVhQT1NFIHt7LlBvcnR9fQpDT1BZIC0tZnJvbT1idWlsZF9iYXNlIC97ey5BUFBOQU1FfX0vYnVpbGQvbGlicy8qIC9vcHQvZWFwL3N0YW5kYWxvbmUvZGVwbG95bWVudHMvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvamF2YWdyYWRsZS9tMmtkZmRldGVjdC5zaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAxNDE0ADEzNzI3NjMyNDQ0ADAyMzcyNgAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjICAgQ29weXJpZ2h0IElCTSBDb3Jwb3JhdGlvbiAyMDIwCiMKIyAgIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwojICAgeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgojICAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyAgIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIyAgIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgICBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyAgIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIyAgIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKIyBUYWtlcyBhcyBpbnB1dCB0aGUgc291cmNlIGRpcmVjdG9yeSBhbmQgcmV0dXJucyBlcnJvciBpZiBpdCBpcyBub3QgZml0CmlmIFsgISAtZiAiJDEvYnVpbGQuZ3JhZGxlIiBdOyB0aGVuCiAgIGV4aXQgMQplbHNlCiAgIGVjaG8gJ3siUG9ydCI6IDgwODAsICJBUFBOQU1FIjogInNpbXBsZXdlYmFwcCJ9JyAKZmkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZG9ja2VyZmlsZXMvamF2YW1hdmVuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAwMDAwADEzNzI3NjMyNDQ0ADAyMDc2NAAgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkb2NrZXJmaWxlcy9qYXZhbWF2ZW4vRG9ja2VyZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDY0NAAwMDAwNzY1ADAwMDAwMjQAMDAwMDAwMDE3MjAAMTM3Mjc2MzI0NDQAMDIzMDM1ACAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHVzdGFyADAwaGFyaWtyaXNobmFuYmFsYWdvcGFsAAAAAAAAAAAAAABzdGFmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAAMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMgICBDb3B5cmlnaHQgSUJNIENvcnBvcmF0aW9uIDIwMjAKIwojICAgTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiMgICB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgICBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKIwojICAgICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKIwojICAgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojICAgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyAgIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgojICAgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojICAgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgojIEJ1aWxkIEFwcApGUk9NIHJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL3ViaTgvdWJpOmxhdGVzdCBBUyBidWlsZF9iYXNlClJVTiB5dW0gaW5zdGFsbCAteSBqYXZhLTEuOC4wLW9wZW5qZGstZGV2ZWwKUlVOIHl1bSBpbnN0YWxsIC15IG1hdmVuCkNPUFkgLiAve3suQVBQTkFNRX19CldPUktESVIgL3t7LkFQUE5BTUV9fQpSVU4gbXZuIHBhY2thZ2UKCiMgUnVuIEFwcApGUk9NIHJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL2pib3NzLWVhcC02L2VhcDY0LW9wZW5zaGlmdDpsYXRlc3QKRVhQT1NFIHt7LlBvcnR9fQpDT1BZIC0tZnJvbT1idWlsZF9iYXNlIC97ey5BUFBOQU1FfX0vdGFyZ2V0LyogL29wdC9lYXAvc3RhbmRhbG9uZS9kZXBsb3ltZW50cy8AAAAA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bGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgICBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAojICAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgICBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgICBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCiMgVGFrZXMgYXMgaW5wdXQgdGhlIHNvdXJjZSBmb2xkZXIgYW5kIHJldHVybnMgZXJyb3IgaWYgaXQgaXMgbm90IGZpdApCQVNFX0RJUj0kMQpOQVRJVkVfSU1BR0U9InJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL3JlZGhhdC1vcGVuamRrLTE4L29wZW5qZGsxOC1vcGVuc2hpZnQ6bGF0ZXN0IgpXRUJfSU1BR0U9InJlZ2lzdHJ5LmFjY2Vzcy5yZWRoYXQuY29tL2pib3NzLWVhcC02L2VhcDY0LW9wZW5zaGlmdDpsYXRlc3QiCgojIEdyYWRsZSBub3Qgc3VwcG9ydGVkIHlldAppZiBbIC1mICIkMS9idWlsZC5ncmFkbGUiIF07IHRoZW4KICAgZXhpdCAxCmZpCgojIEFudCBub3Qgc3VwcG9ydGVkIHlldAppZiBbIC1mICIkMS9idWlsZC54bWwiIF07IHRoZW4KICAgZXhpdCAxCmZpCgppZiBbIC1mICIkMS9wb20ueG1sIiBdOyB0aGVuCiAgIGVjaG8gJ3siQnVpbGRlciI6ICInJFdFQl9JTUFHRSciLCAiUG9ydCI6IDgwODB9JwogICBleGl0IDAKZmkKCmZvdW5kPWBmaW5kICRCQVNFX0RJUi8uIC1uYW1lICIqLmphdmEiIC1wcmludCAtcXVpdCB8IHdjIC1sYAoKaWYgWyAkZm91bmQgLWVxIDEgXTsgdGhlbgogICAgZWNobyAneyJCdWlsZGVyIjogIickTkFU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YXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgICBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgICBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAojICAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgICBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgICBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCiMgVGFrZXMgYXMgaW5wdXQgdGhlIHNvdXJjZSBmb2xkZXIgYW5kIHJldHVybnMgZXJyb3IgaWYgaXQgaXMgbm90IGZpdApCQVNFX0RJUj0kMQpJTUFHRT0icmVnaXN0cnkuYWNjZXNzLnJlZGhhdC5jb20vcmhzY2wvcnVieS0yNS1yaGVsNzpsYXRlc3QiCgppZiBbIC1mICRCQVNFX0RJUi9HZW1maWxlIF07IHRoZW4KICAgIGVjaG8gJ3siQnVpbGRlciI6ICInJElNQUdFJyIsICJQb3J0IjogODA4MH0nCmVsc2UgCiAgICBleGl0IDEKZmk=` diff --git a/internal/assets/dockerfiles/django/Dockerfile b/internal/assets/dockerfiles/django/Dockerfile new file mode 100644 index 000000000..eed65657d --- /dev/null +++ b/internal/assets/dockerfiles/django/Dockerfile @@ -0,0 +1,21 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +FROM registry.access.redhat.com/ubi8/python-36 + +WORKDIR /app +COPY . . +RUN pip install -r requirements.txt +EXPOSE {{.Port}} +CMD ["python", "manage.py", "runserver", "{{.BINDING}}"] \ No newline at end of file diff --git a/internal/assets/dockerfiles/django/m2kdfdetect.sh b/internal/assets/dockerfiles/django/m2kdfdetect.sh new file mode 100755 index 000000000..931005536 --- /dev/null +++ b/internal/assets/dockerfiles/django/m2kdfdetect.sh @@ -0,0 +1,22 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 + +if [ ! -f "$1/Pipfile" ]; then + exit 1 +else + echo '{"Port": 8080, "BINDING": "0.0.0.0:8080"}' +fi \ No newline at end of file diff --git a/internal/assets/dockerfiles/golang/Dockerfile b/internal/assets/dockerfiles/golang/Dockerfile new file mode 100644 index 000000000..e7e5262cb --- /dev/null +++ b/internal/assets/dockerfiles/golang/Dockerfile @@ -0,0 +1,36 @@ +# Copyright IBM Corporation 2020 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build App +FROM registry.access.redhat.com/ubi8/ubi:latest AS build_base + +WORKDIR /temp +ENV GOPATH=/go +ENV PATH=$GOPATH/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +RUN curl -o go.tar.gz https://dl.google.com/go/go1.15.linux-amd64.tar.gz +RUN tar -xzf go.tar.gz && mv go /usr/local/ +RUN yum install git make -y +RUN mkdir -p $GOPATH/src $GOPATH/bin && chmod -R 777 $GOPATH +WORKDIR /{{.APPNAME}} +COPY . . +RUN go build -o {{.APPNAME}} +RUN cp ./{{.APPNAME}} /bin/{{.APPNAME}} + +# Run App +FROM registry.access.redhat.com/ubi8/ubi:latest +COPY --from=build_base /bin/{{.APPNAME}} /bin/{{.APPNAME}} + +EXPOSE {{.Port}} + +CMD ["{{.APPNAME}}"] \ No newline at end of file diff --git a/internal/assets/dockerfiles/golang/m2kdfdetect.sh b/internal/assets/dockerfiles/golang/m2kdfdetect.sh new file mode 100755 index 000000000..6652330ab --- /dev/null +++ b/internal/assets/dockerfiles/golang/m2kdfdetect.sh @@ -0,0 +1,22 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +found=`find $1/. -name "*.go" -print -quit | wc -l` + +if [ ! $found -eq 1 ]; then + exit 1 +else + echo '{"Port": 8080, "APPNAME": "app-bin"}' +fi diff --git a/internal/assets/dockerfiles/javaant/Dockerfile b/internal/assets/dockerfiles/javaant/Dockerfile new file mode 100644 index 000000000..ecd081a8b --- /dev/null +++ b/internal/assets/dockerfiles/javaant/Dockerfile @@ -0,0 +1,28 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +FROM registry.access.redhat.com/ubi8/ubi:latest AS build_base +RUN yum install -y java-1.8.0-openjdk-devel +RUN yum install -y wget +RUN yum install -y unzip +RUN wget https://mirrors.estointernet.in/apache//ant/binaries/apache-ant-1.10.8-bin.zip -P /tmp +RUN unzip -d /opt/apache-ant /tmp/apache-ant-1.10.8-bin.zip +ENV PATH="${PATH}:/opt/apache-ant/apache-ant-1.10.8/bin/" +COPY . /{{.APPNAME}} +WORKDIR /{{.APPNAME}} +RUN {{.ANTCMD}} + +FROM registry.access.redhat.com/jboss-eap-6/eap64-openshift:latest +EXPOSE {{.Port}} +COPY --from=build_base /{{.APPNAME}}/output/{{.APPNAME}}.ear /opt/eap/standalone/deployments/ \ No newline at end of file diff --git a/internal/assets/dockerfiles/javaant/m2kdfdetect.sh b/internal/assets/dockerfiles/javaant/m2kdfdetect.sh new file mode 100755 index 000000000..d5e6be6a9 --- /dev/null +++ b/internal/assets/dockerfiles/javaant/m2kdfdetect.sh @@ -0,0 +1,20 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +if [ ! -f "$1/build.xml" ]; then + exit 1 +else + echo '{"Port": 8080, "ANTCMD": "ant all", "APPNAME": "simplewebapp"}' +fi \ No newline at end of file diff --git a/internal/assets/dockerfiles/javagradle/Dockerfile b/internal/assets/dockerfiles/javagradle/Dockerfile new file mode 100644 index 000000000..abeb59125 --- /dev/null +++ b/internal/assets/dockerfiles/javagradle/Dockerfile @@ -0,0 +1,30 @@ +# Copyright IBM Corporation 2020 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build App +FROM registry.access.redhat.com/ubi8/ubi:latest AS build_base +RUN yum install -y java-1.8.0-openjdk-devel +RUN yum install -y wget +RUN yum install -y unzip +RUN wget https://services.gradle.org/distributions/gradle-6.6-bin.zip -P /tmp +RUN unzip -d /opt/gradle /tmp/gradle-6.6-bin.zip +ENV PATH="${PATH}:/opt/gradle/gradle-6.6/bin/" +COPY . /{{.APPNAME}} +WORKDIR /{{.APPNAME}} +RUN gradle build + +# Run App +FROM registry.access.redhat.com/jboss-eap-6/eap64-openshift:latest +EXPOSE {{.Port}} +COPY --from=build_base /{{.APPNAME}}/build/libs/* /opt/eap/standalone/deployments/ \ No newline at end of file diff --git a/internal/assets/dockerfiles/javagradle/m2kdfdetect.sh b/internal/assets/dockerfiles/javagradle/m2kdfdetect.sh new file mode 100755 index 000000000..2059eb207 --- /dev/null +++ b/internal/assets/dockerfiles/javagradle/m2kdfdetect.sh @@ -0,0 +1,20 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +if [ ! -f "$1/build.gradle" ]; then + exit 1 +else + echo '{"Port": 8080, "APPNAME": "simplewebapp"}' +fi \ No newline at end of file diff --git a/internal/assets/dockerfiles/javamaven/Dockerfile b/internal/assets/dockerfiles/javamaven/Dockerfile new file mode 100644 index 000000000..57a0930b3 --- /dev/null +++ b/internal/assets/dockerfiles/javamaven/Dockerfile @@ -0,0 +1,26 @@ +# Copyright IBM Corporation 2020 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build App +FROM registry.access.redhat.com/ubi8/ubi:latest AS build_base +RUN yum install -y java-1.8.0-openjdk-devel +RUN yum install -y maven +COPY . /{{.APPNAME}} +WORKDIR /{{.APPNAME}} +RUN mvn package + +# Run App +FROM registry.access.redhat.com/jboss-eap-6/eap64-openshift:latest +EXPOSE {{.Port}} +COPY --from=build_base /{{.APPNAME}}/target/* /opt/eap/standalone/deployments/ \ No newline at end of file diff --git a/internal/assets/dockerfiles/javamaven/m2kdfdetect.sh b/internal/assets/dockerfiles/javamaven/m2kdfdetect.sh new file mode 100755 index 000000000..06a8f39c8 --- /dev/null +++ b/internal/assets/dockerfiles/javamaven/m2kdfdetect.sh @@ -0,0 +1,20 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +if [ ! -f "$1/pom.xml" ]; then + exit 1 +else + echo '{"Port": 8080, "APPNAME": "app"}' +fi \ No newline at end of file diff --git a/internal/assets/dockerfiles/nodejs/Dockerfile b/internal/assets/dockerfiles/nodejs/Dockerfile new file mode 100644 index 000000000..496027e20 --- /dev/null +++ b/internal/assets/dockerfiles/nodejs/Dockerfile @@ -0,0 +1,20 @@ +# Copyright IBM Corporation 2020 +# +# 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. + + +FROM registry.access.redhat.com/ubi8/nodejs-12 +ADD . . +RUN npm install +EXPOSE {{.Port}} +CMD npm run -d start \ No newline at end of file diff --git a/internal/assets/dockerfiles/nodejs/m2kdfdetect.sh b/internal/assets/dockerfiles/nodejs/m2kdfdetect.sh new file mode 100755 index 000000000..302bc045b --- /dev/null +++ b/internal/assets/dockerfiles/nodejs/m2kdfdetect.sh @@ -0,0 +1,20 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +if [ ! -f "$1/package.json" ]; then + exit 1 +else + echo '{"Port": 8080, "APPNAME": "app"}' +fi \ No newline at end of file diff --git a/internal/assets/dockerfiles/php/Dockerfile b/internal/assets/dockerfiles/php/Dockerfile new file mode 100644 index 000000000..4a40b70f5 --- /dev/null +++ b/internal/assets/dockerfiles/php/Dockerfile @@ -0,0 +1,25 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +FROM registry.access.redhat.com/ubi8/ubi:latest + +RUN yum update -y +RUN yum install -y php + +RUN mkdir -p /{{.APPNAME}} +COPY . /{{.APPNAME}} +WORKDIR /{{.APPNAME}} +RUN cd /{{.APPNAME}} +EXPOSE {{.Port}} +CMD ["php", "-S", "{{.BINDING}}"] \ No newline at end of file diff --git a/internal/assets/dockerfiles/php/m2kdfdetect.sh b/internal/assets/dockerfiles/php/m2kdfdetect.sh new file mode 100755 index 000000000..033ec3b50 --- /dev/null +++ b/internal/assets/dockerfiles/php/m2kdfdetect.sh @@ -0,0 +1,24 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 + +found=`find $BASE_DIR/. -name "*.php" -print -quit | wc -l` + +if [ $found -eq 1 ]; then + echo '{"Port": 8080, "BINDING": "0.0.0.0:8080", "APPNAME": "app"}' +else + exit 1 +fi diff --git a/internal/assets/dockerfiles/python/Dockerfile b/internal/assets/dockerfiles/python/Dockerfile new file mode 100644 index 000000000..9913e2ec7 --- /dev/null +++ b/internal/assets/dockerfiles/python/Dockerfile @@ -0,0 +1,22 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +FROM registry.access.redhat.com/ubi8/python-36 +COPY . /{{.APPNAME}} +RUN mkdir -p /{{.APPNAME}} +WORKDIR /{{.APPNAME}} +RUN cd /{{.APPNAME}} +RUN pip install -r requirements.txt +EXPOSE {{.Port}} +CMD ["python", "/{{.APPNAME}}/{{.MAINSCRIPT}}"] \ No newline at end of file diff --git a/internal/assets/dockerfiles/python/m2kdfdetect.sh b/internal/assets/dockerfiles/python/m2kdfdetect.sh new file mode 100755 index 000000000..b54dc6e42 --- /dev/null +++ b/internal/assets/dockerfiles/python/m2kdfdetect.sh @@ -0,0 +1,28 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +SPECIAL_FILES=($BASE_DIR/requirements.txt $BASE_DIR/setup.py $BASE_DIR/environment.yml $BASE_DIR/Pipfile) + +for fileName in "${SPECIAL_FILES[@]}" +do + if [ -f "$fileName" ]; then + startScript=`grep -lRe "__main__" $1 | awk '{print $1}' | xargs -n1 basename` + echo '{"MAINSCRIPT": "'$startScript'", "APPNAME": "app", "Port": 8080}' + exit 0 + fi +done + +exit 1 \ No newline at end of file diff --git a/internal/assets/s2i/golang/.s2i/environment b/internal/assets/s2i/golang/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/golang/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/golang/m2ks2idetect.sh b/internal/assets/s2i/golang/m2ks2idetect.sh new file mode 100644 index 000000000..67f042a9e --- /dev/null +++ b/internal/assets/s2i/golang/m2ks2idetect.sh @@ -0,0 +1,29 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +IMAGE="registry.access.redhat.com/ubi8/go-toolset:latest" + +if [ ! -f "$1/go.mod" ]; then + found=`find $BASE_DIR/. -name "*.go" -print -quit | wc -l` + + if [ $found -eq 1 ]; then + echo '{"Builder": "'$IMAGE'", "Port": 8080}' + else + exit 1 + fi +else + echo '{"Builder": "'$IMAGE'", "Port": 8080}' +fi \ No newline at end of file diff --git a/internal/assets/s2i/java/.s2i/environment b/internal/assets/s2i/java/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/java/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/java/m2ks2idetect.sh b/internal/assets/s2i/java/m2ks2idetect.sh new file mode 100644 index 000000000..9ae11f7df --- /dev/null +++ b/internal/assets/s2i/java/m2ks2idetect.sh @@ -0,0 +1,41 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +NATIVE_IMAGE="registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift:latest" +WEB_IMAGE="registry.access.redhat.com/jboss-eap-6/eap64-openshift:latest" + +# Gradle not supported yet +if [ -f "$1/build.gradle" ]; then + exit 1 +fi + +# Ant not supported yet +if [ -f "$1/build.xml" ]; then + exit 1 +fi + +if [ -f "$1/pom.xml" ]; then + echo '{"Builder": "'$WEB_IMAGE'", "Port": 8080}' + exit 0 +fi + +found=`find $BASE_DIR/. -name "*.java" -print -quit | wc -l` + +if [ $found -eq 1 ]; then + echo '{"Builder": "'$NATIVE_IMAGE'", "Port": 8080}' +else + exit 1 +fi \ No newline at end of file diff --git a/internal/assets/s2i/nodejs/.s2i/environment b/internal/assets/s2i/nodejs/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/nodejs/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/nodejs/m2ks2idetect.sh b/internal/assets/s2i/nodejs/m2ks2idetect.sh new file mode 100755 index 000000000..34567a7b7 --- /dev/null +++ b/internal/assets/s2i/nodejs/m2ks2idetect.sh @@ -0,0 +1,21 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source directory and returns error if it is not fit +if [ ! -f "$1/package.json" ]; then + exit 1 +fi + +IMAGE="registry.access.redhat.com/ubi8/nodejs-10" +echo '{"Builder": "'$IMAGE'", "Port": 8080}' \ No newline at end of file diff --git a/internal/assets/s2i/php/.s2i/environment b/internal/assets/s2i/php/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/php/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/php/m2ks2idetect.sh b/internal/assets/s2i/php/m2ks2idetect.sh new file mode 100644 index 000000000..198b739dc --- /dev/null +++ b/internal/assets/s2i/php/m2ks2idetect.sh @@ -0,0 +1,25 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +IMAGE="registry.access.redhat.com/rhscl/php-72-rhel7:latest" + +found=`find $BASE_DIR/. -name "*.php" -print -quit | wc -l` + +if [ $found -eq 1 ]; then + echo '{"Builder": "'$IMAGE'", "Port": 8080}' +else + exit 1 +fi \ No newline at end of file diff --git a/internal/assets/s2i/python/.s2i/environment b/internal/assets/s2i/python/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/python/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/python/m2ks2idetect.sh b/internal/assets/s2i/python/m2ks2idetect.sh new file mode 100755 index 000000000..6b082bfeb --- /dev/null +++ b/internal/assets/s2i/python/m2ks2idetect.sh @@ -0,0 +1,29 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +SPECIAL_FILES=($BASE_DIR/requirements.txt $BASE_DIR/setup.py $BASE_DIR/environment.yml $BASE_DIR/Pipfile) +IMAGE="registry.access.redhat.com/rhscl/python-36-rhel7:latest" + +for fileName in "${SPECIAL_FILES[@]}" +do + if [ -f "$fileName" ]; then + startScript=`grep -lRe "__main__" $BASE_DIR | awk '{print $1}' | xargs -n1 basename` + echo '{"Builder": "'$IMAGE'", "APP_FILE": "'$startScript'", "Port": 8080}' + exit 0 + fi +done + +exit 1 \ No newline at end of file diff --git a/internal/assets/s2i/ruby/.s2i/environment b/internal/assets/s2i/ruby/.s2i/environment new file mode 100644 index 000000000..89d4f223f --- /dev/null +++ b/internal/assets/s2i/ruby/.s2i/environment @@ -0,0 +1,5 @@ +{{- range $key, $value := . }} +{{- if and (ne $key "Builder") (ne $key "ImageName") (ne $key "Port") }} +{{ $key }}={{ $value }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/internal/assets/s2i/ruby/m2ks2idetect.sh b/internal/assets/s2i/ruby/m2ks2idetect.sh new file mode 100644 index 000000000..b951318a4 --- /dev/null +++ b/internal/assets/s2i/ruby/m2ks2idetect.sh @@ -0,0 +1,23 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Takes as input the source folder and returns error if it is not fit +BASE_DIR=$1 +IMAGE="registry.access.redhat.com/rhscl/ruby-25-rhel7:latest" + +if [ -f $BASE_DIR/Gemfile ]; then + echo '{"Builder": "'$IMAGE'", "Port": 8080}' +else + exit 1 +fi \ No newline at end of file diff --git a/internal/collector/cfappscollector.go b/internal/collector/cfappscollector.go new file mode 100644 index 000000000..38bf9310e --- /dev/null +++ b/internal/collector/cfappscollector.go @@ -0,0 +1,102 @@ +/* +Copyright IBM Corporation 2020 + +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 collector + +import ( + "encoding/json" + "os" + "os/exec" + "path/filepath" + + log "github.com/sirupsen/logrus" + + sourcetypes "github.com/konveyor/move2kube/internal/collector/sourcetypes" + common "github.com/konveyor/move2kube/internal/common" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +// CfAppsCollector collects cf runtime applications +type CfAppsCollector struct { +} + +// GetAnnotations returns annotations on which this collector should be invoked +func (c *CfAppsCollector) GetAnnotations() []string { + annotations := []string{"cf", "cloudfoundry"} + return annotations +} + +//Collect gets the cf app metadata by querying the cf app. Assumes that the authentication with cluster is already done. +func (c *CfAppsCollector) Collect(inputPath string, outputPath string) error { + + //To run: cf curl /v2/apps/ + cmd := exec.Command("cf", "curl", "/v2/apps") + output, err := cmd.Output() + if err != nil { + log.Errorf("%s", err.Error()) + return err + } + log.Debugf("Cf Curl output %s", output) + sourcecfinstanceapps := sourcetypes.CfInstanceApps{} + err = json.Unmarshal([]byte(output), &sourcecfinstanceapps) + if err != nil { + log.Errorf("Error in unmarshalling yaml: %s. Skipping.", err) + return err + } + outputPath = filepath.Join(outputPath, "cf") + err = os.MkdirAll(outputPath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create outputPath %s : %s", outputPath, err) + } + cfinstanceapps := collecttypes.NewCfInstanceApps() + cfinstanceapps.Spec.CfApplications = []collecttypes.CfApplication{} + fileName := "instanceapps_" + + log.Debugf("Detected %d apps", len(sourcecfinstanceapps.CfResources)) + for _, sourcecfapp := range sourcecfinstanceapps.CfResources { + app := collecttypes.CfApplication{} + app.Name = sourcecfapp.CfAppEntity.Name + log.Debugf("Reading info about %s", app.Name) + + if sourcecfapp.CfAppEntity.Buildpack != "null" { + app.Buildpack = sourcecfapp.CfAppEntity.Buildpack + } + if sourcecfapp.CfAppEntity.DetectedBuildpack != "null" { + app.DetectedBuildpack = sourcecfapp.CfAppEntity.DetectedBuildpack + } + if sourcecfapp.CfAppEntity.DockerImage != "null" { + app.DockerImage = sourcecfapp.CfAppEntity.DockerImage + } + app.Instances = sourcecfapp.CfAppEntity.Instances + app.Memory = sourcecfapp.CfAppEntity.Memory + app.Env = sourcecfapp.CfAppEntity.Env + app.Ports = sourcecfapp.CfAppEntity.Ports + cfinstanceapps.Spec.CfApplications = append(cfinstanceapps.Spec.CfApplications, app) + + fileName = fileName + app.Name + } + + if fileName != "" { + outputPath = filepath.Join(outputPath, common.NormalizeForFilename(fileName)+".yaml") + err = common.WriteYaml(outputPath, cfinstanceapps) + if err != nil { + log.Errorf("Unable to write collect output : %s", err) + } + return err + } + + return nil +} diff --git a/internal/collector/cfcontainertypescollector.go b/internal/collector/cfcontainertypescollector.go new file mode 100644 index 000000000..c477f9611 --- /dev/null +++ b/internal/collector/cfcontainertypescollector.go @@ -0,0 +1,211 @@ +/* +Copyright IBM Corporation 2020 + +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 collector + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + + sourcetypes "github.com/konveyor/move2kube/internal/collector/sourcetypes" + common "github.com/konveyor/move2kube/internal/common" + containerize "github.com/konveyor/move2kube/internal/containerizer" + source "github.com/konveyor/move2kube/internal/source" + collecttypes "github.com/konveyor/move2kube/types/collection" + "github.com/konveyor/move2kube/types/plan" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// CFContainerTypesCollector collects buildpacks supported by the instance +type CFContainerTypesCollector struct { +} + +// GetAnnotations returns annotations on which this collector should be invoked +func (c *CFContainerTypesCollector) GetAnnotations() []string { + annotations := []string{"cloudfoundry", "cf"} + return annotations +} + +//Collect gets the cf containerization types +func (c *CFContainerTypesCollector) Collect(inputDirectory string, outputPath string) error { + //Creating the output sub-directory if it does not exist + outputPath = filepath.Join(outputPath, "cf") + err := os.MkdirAll(outputPath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create output path %s : %s", outputPath, err) + return err + } + var cfcontainerizers = collecttypes.NewCfContainerizers() + cfcontainerizers.Spec.BuildpackContainerizers = []collecttypes.BuildpackContainerizer{} + buildpackNames := getCfbuildpackNames(inputDirectory) + log.Debugf("buildpackNames : %s", buildpackNames) + cnbcontainerizer := new(containerize.CNBContainerizer) + //TODO: How do you also load existing builders that are currently being collected by cnbbuildercollector? + cnbcontainerizer.Init("") + buildpacks := cnbcontainerizer.GetAllBuildpacks() + log.Debugf("buildpacks : %s", buildpacks) + fileName := "cfcontainertypes_" + for _, buildpackName := range buildpackNames { + buildpackcontainerizer := getBuildpackContainerizer(buildpackName, buildpacks) + cfcontainerizers.Spec.BuildpackContainerizers = append(cfcontainerizers.Spec.BuildpackContainerizers, buildpackcontainerizer) + fileName = fileName + buildpackName + } + if fileName != "" { + outputPath = filepath.Join(outputPath, common.NormalizeForFilename(fileName)+".yaml") + err := common.WriteYaml(outputPath, cfcontainerizers) + if err != nil { + log.Errorf("Unable to write cf container type output %s : %s", fileName, err) + } + return err + } + return fmt.Errorf("No buildpacks found") +} + +func getBuildpackContainerizer(buildpackName string, options map[string][]string) collecttypes.BuildpackContainerizer { //[containerization taregt option][builder] + buildpackcontainerizer := collecttypes.BuildpackContainerizer{} + buildpackcontainerizer.ContainerBuildType = plan.CNBContainerBuildTypeValue + buildpackcontainerizer.BuildpackName = buildpackName + bpoptions := make(map[string]string) + bps := []string{} + for targetoption, buildpacks := range options { + option := common.GetClosestMatchingString(buildpacks, buildpackName) + if _, ok := bpoptions[option]; !ok { + bpoptions[option] = targetoption + bps = append(bps, option) + } + } + bp := common.GetClosestMatchingString(bps, buildpackName) + buildpackcontainerizer.ContainerizationTargetOptions = []string{bpoptions[bp]} + return buildpackcontainerizer +} + +func getCfbuildpackNames(inputPath string) []string { + buildpacks := []string{} + if inputPath != "" { + bps, err := getAllUsedBuildpacks(inputPath) + if err != nil { + log.Warnf("Unable to find used buildpacks : %s", err) + } else { + for _, buildpack := range bps { + if !common.IsStringPresent(buildpacks, buildpack) { + buildpacks = append(buildpacks, buildpack) + } + } + } + } else { + bps, err := getAllCfInstanceBuildpacks() + if err != nil { + log.Warnf("Unable to collect buildpacks from cf instance : %s", err) + } else { + for _, buildpack := range bps { + if !common.IsStringPresent(buildpacks, buildpack) { + buildpacks = append(buildpacks, buildpack) + } + } + } + + bps, err = getAllCfAppBuildpacks() + if err != nil { + log.Warnf("Unable to find used buildpacks : %s", err) + } else { + for _, buildpack := range bps { + if !common.IsStringPresent(buildpacks, buildpack) { + buildpacks = append(buildpacks, buildpack) + } + } + } + } + return buildpacks +} + +func getAllCfInstanceBuildpacks() ([]string, error) { + var buildpacks []string + cmd := exec.Command("cf", "buildpacks") + outputStr, err := cmd.Output() + if err != nil { + log.Warnf("Error while getting buildpacks : %s", err) + return nil, err + } + lines := strings.Split(string(outputStr), "\n") + for _, line := range lines { + if line == "Getting buildpacks..." { + continue + } + buildpackmatches := strings.Fields(string(line)) + if len(buildpackmatches) == 0 || buildpackmatches[0] == "buildpack" { + continue + } + buildpacks = append(buildpacks, buildpackmatches[0]) + + } + return buildpacks, nil +} + +func getAllCfAppBuildpacks() ([]string, error) { + var buildpacks []string + cmd := exec.Command("cf", "curl", "/v2/apps") + output, err := cmd.Output() + if err != nil { + log.Errorf("%s", err.Error()) + return nil, err + } + log.Debugf("Cf Curl output %s", output) + sourcecfinstanceapps := sourcetypes.CfInstanceApps{} + err = json.Unmarshal([]byte(output), &sourcecfinstanceapps) + if err != nil { + log.Errorf("Error in unmarshalling yaml: %s. Skipping", err) + return nil, err + } + + log.Debugf("Detected %d apps", len(sourcecfinstanceapps.CfResources)) + for _, sourcecfapp := range sourcecfinstanceapps.CfResources { + if sourcecfapp.CfAppEntity.Buildpack != "" { + buildpacks = append(buildpacks, sourcecfapp.CfAppEntity.Buildpack) + } + if sourcecfapp.CfAppEntity.DetectedBuildpack != "" { + buildpacks = append(buildpacks, sourcecfapp.CfAppEntity.DetectedBuildpack) + } + } + return buildpacks, nil +} + +func getAllUsedBuildpacks(directorypath string) ([]string, error) { + var buildpacks []string + files, err := common.GetFilesByExt(directorypath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize application manifest yamls : %s", err) + } + for _, fullpath := range files { + applications, _, err := source.ReadApplicationManifest(fullpath, "", plantypes.Yamls) + if err != nil { + log.Debugf("Error while trying to parse manifest : %s", err) + continue + } + for _, application := range applications { + if application.Buildpack.IsSet { + buildpacks = append(buildpacks, application.Buildpack.Value) + } + buildpacks = append(buildpacks, application.Buildpacks...) + } + } + return buildpacks, nil +} diff --git a/internal/collector/clustercollector.go b/internal/collector/clustercollector.go new file mode 100644 index 000000000..0d45621aa --- /dev/null +++ b/internal/collector/clustercollector.go @@ -0,0 +1,648 @@ +/* +Copyright IBM Corporation 2020 + +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 collector + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime/debug" + "sort" + "strings" + + semver "github.com/Masterminds/semver/v3" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/konveyor/move2kube/internal/apiresourceset" + common "github.com/konveyor/move2kube/internal/common" + collecttypes "github.com/konveyor/move2kube/types/collection" + cgdiscovery "k8s.io/client-go/discovery" + "k8s.io/client-go/kubernetes/scheme" + cgclientcmd "k8s.io/client-go/tools/clientcmd" +) + +//ClusterCollector Implements Collector interface +type ClusterCollector struct { + clusterCmd string +} + +// GetAnnotations returns annotations on which this collector should be invoked +func (c ClusterCollector) GetAnnotations() []string { + annotations := []string{"k8s"} + return annotations +} + +//Collect gets the cluster metadata by querying the cluster. Assumes that the authentication with cluster is already done. +func (c *ClusterCollector) Collect(inputPath string, outputPath string) error { + + //Creating the output sub-directory if it does not exist + outputPath = filepath.Join(outputPath, "clusters") + err := os.MkdirAll(outputPath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create outputpath %s : %s", outputPath, err) + return err + } + cmd := c.getClusterCommand() + if cmd == "" { + errStr := "No Kubectl or oc in path. Add Kubectl to path and rerun to collect data about the cluster in context." + log.Warnf(errStr) + return fmt.Errorf(errStr) + } + name, err := c.getClusterContextName() + if err != nil { + log.Warnf("Unable to access cluster in context : %s", err) + return err + } + clusterMd := collecttypes.NewClusterMetadata(name) + clusterMd.Spec.StorageClasses, err = c.getStorageClasses() + if err != nil { + //If no storage classes, this will be an empty array + clusterMd.Spec.StorageClasses = []string{} + } + + APIKindVersionMap, err := c.collectUsingAPI() + if err != nil { + log.Warnf("Falling back to using CLI based collect") + clusterMd.Spec.APIKindVersionMap, err = c.collectUsingCLI() + if err != nil { + return err + } + } else { + clusterMd.Spec.APIKindVersionMap = APIKindVersionMap + } + + c.groupOrderPolicy(&clusterMd.Spec.APIKindVersionMap) + //c.VersionOrderPolicy(&clusterMd.APIKindVersionMap) + + outputPath = filepath.Join(outputPath, common.NormalizeForFilename(clusterMd.Name)+".yaml") + err = common.WriteYaml(outputPath, clusterMd) + return err +} + +func (c *ClusterCollector) getClusterCommand() string { + if c.clusterCmd == "" { + cmd := "kubectl" + _, err := exec.LookPath(cmd) + if err != nil { + log.Warnf("Unable to find "+cmd+" : %v", err) + cmd = "oc" + _, err := exec.LookPath(cmd) + if err != nil { + log.Warnf("Unable to find "+cmd+" : %v", err) + } else { + c.clusterCmd = cmd + } + } else { + c.clusterCmd = cmd + } + } + return c.clusterCmd +} + +func (c *ClusterCollector) getClusterContextName() (string, error) { + cmd := exec.Command(c.getClusterCommand(), "config", "current-context") + name, err := cmd.Output() + return string(name), err +} + +func (c *ClusterCollector) getStorageClasses() ([]string, error) { + ccmd := c.getClusterCommand() + cmd := exec.Command(ccmd, "get", "sc", "-o", "yaml") + yamlOutput, err := cmd.CombinedOutput() + if err != nil { + errDesc := c.interpretError(string(yamlOutput)) + if errDesc != "" { + log.Warnf("Error while running %s. %s", ccmd, errDesc) + } else { + log.Warnf("Error while fetching storage classes using command [%s]", cmd) + } + return nil, err + } + + var fileContents map[string]interface{} + err = yaml.Unmarshal(yamlOutput, &fileContents) + if err != nil { + log.Errorf("Error in unmarshalling yaml: %s. Skipping.", err) + return nil, err + } + + scArray := fileContents["items"].([]interface{}) + var storageClasses []string + + for _, sc := range scArray { + if mapSC, ok := sc.(map[string]interface{}); ok { + storageClasses = append(storageClasses, mapSC["metadata"].(map[string]interface{})["name"].(string)) + } else { + log.Warnf("Unknown type detected in cluster metadata [%T]", mapSC) + } + } + + return storageClasses, nil +} + +func (c *ClusterCollector) interpretError(cmdOutput string) string { + errorTerms := []string{"Unauthorized", "Username"} + + for _, e := range errorTerms { + if c.getClusterCommand() == "oc" && strings.Contains(cmdOutput, e) { + return "Please login to cluster before running collect. (e.g. oc login --token=)" + } else if c.getClusterCommand() == "kubectl" && strings.Contains(cmdOutput, e) { + return "Please configure the cluster authentication with following instructions: [https://kubernetes.io/docs/reference/kubectl/cheatsheet/#kubectl-context-and-configuration]" + } + } + + return "" +} + +func (c ClusterCollector) getGlobalGroupOrder() []string { + return []string{`^.+\.openshift\.io$`, `^.+\.k8s\.io$`, `^apps$`, `^extensions$`} +} + +func (c *ClusterCollector) getAPI() (*cgdiscovery.DiscoveryClient, error) { + rules := cgclientcmd.NewDefaultClientConfigLoadingRules() + cfg, err := cgclientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &cgclientcmd.ConfigOverrides{}).ClientConfig() + if err != nil { + return nil, err + } + + return cgdiscovery.NewDiscoveryClientForConfig(cfg) +} + +func (c *ClusterCollector) getPreferredResourceUsingAPI(api *cgdiscovery.DiscoveryClient) ([]schema.GroupVersion, error) { + defer func() []schema.GroupVersion { + if rErr := recover(); rErr != nil { + log.Errorf("Recovered from error in getPreferredResourceUsingAPI [%s]", rErr) + return nil + } + return []schema.GroupVersion{} + }() + debug.SetPanicOnFault(true) + var gvList []schema.GroupVersion + if api == nil { + log.Errorf("API object is null") + return nil, fmt.Errorf("API object is null") + } + apiGroupList, err := api.ServerGroups() + if err != nil { + log.Errorf("API request for server-group list failed") + return nil, err + } + for _, group := range apiGroupList.Groups { + preferredGV, err := schema.ParseGroupVersion(group.PreferredVersion.GroupVersion) + if err != nil { + continue + } + gvList = append(gvList, preferredGV) + prioritizedGVList := scheme.Scheme.PrioritizedVersionsForGroup(group.Name) + for _, prioritizedGV := range prioritizedGVList { + if strings.Compare(group.PreferredVersion.GroupVersion, prioritizedGV.String()) == 0 { + continue + } + for _, gvObj := range group.Versions { + if strings.Compare(prioritizedGV.String(), gvObj.GroupVersion) == 0 { + gv, _ := schema.ParseGroupVersion(gvObj.GroupVersion) + gvList = append(gvList, gv) + break + } + } + } + for _, version := range group.Versions { + gv, _ := schema.ParseGroupVersion(version.GroupVersion) + if gvExists(gvList, gv) { + continue + } + gvList = append(gvList, gv) + } + } + return gvList, nil +} + +func (c *ClusterCollector) getKindsForGroups(api *cgdiscovery.DiscoveryClient) (map[string][]schema.GroupVersion, error) { + defer func() map[string][]schema.GroupVersion { + if rErr := recover(); rErr != nil { + log.Errorf("Recovered from error in getKindsForGroups [%s]", rErr) + return nil + } + var emptyMap map[string][]schema.GroupVersion + return emptyMap + }() + + mapKind := make(map[string][]schema.GroupVersion) + + _, apiResourceList, err := api.ServerGroupsAndResources() + if err != nil { + return nil, err + } + + for _, rscListObj := range apiResourceList { + gvObj, err := schema.ParseGroupVersion(rscListObj.GroupVersion) + for err != nil { + log.Warnf("Ignoring group-version [%s]. Could not parse it", rscListObj.GroupVersion) + continue + } + + for _, rscObj := range rscListObj.APIResources { + if gvList, ok := mapKind[rscObj.Kind]; ok { + if !gvExists(gvList, gvObj) { + gvList = append(gvList, gvObj) + } + mapKind[rscObj.Kind] = gvList + } else { + mapKind[rscObj.Kind] = []schema.GroupVersion{gvObj} + + } + } + } + + return mapKind, nil +} + +func (c *ClusterCollector) sortGroupVersionByPreferrence(prefGVList []schema.GroupVersion, mapKind *map[string][]schema.GroupVersion) { + for kind, gvList := range *mapKind { + var gvOrderedList []schema.GroupVersion + unorderedList := []string{} + for _, pGV := range prefGVList { + if gvExists(gvList, pGV) { + gvOrderedList = append(gvOrderedList, pGV) + } else { + unorderedList = append(unorderedList, pGV.String()) + } + } + + unorderedList = c.clusterByGroupsAndSortVersions(unorderedList) + for _, gvStr := range unorderedList { + gvObj, err := schema.ParseGroupVersion(gvStr) + if err == nil { + continue + } + gvOrderedList = append(gvOrderedList, gvObj) + } + (*mapKind)[kind] = gvOrderedList + } +} + +func (c *ClusterCollector) collectUsingAPI() (map[string][]string, error) { + api, err := c.getAPI() + if err != nil { + log.Warnf("Failed to api handle for cluster") + return nil, err + } + + gvList, err := c.getPreferredResourceUsingAPI(api) + errStr := "Failed to retrieve preferred group information from cluster" + if err != nil { + log.Warnf(errStr) + return nil, err + } else if len(gvList) == 0 { + log.Warnf(errStr) + return nil, fmt.Errorf(errStr) + } + + mapKind, err := c.getKindsForGroups(api) + errStr = "Failed to retrieve information from cluster" + if err != nil { + log.Warnf(errStr) + return nil, err + } else if len(mapKind) == 0 { + log.Warnf(errStr) + return nil, fmt.Errorf(errStr) + } + + c.sortGroupVersionByPreferrence(gvList, &mapKind) + + APIKindVersionMap := make(map[string][]string) + + for kind, gvList := range mapKind { + gvStrList := make([]string, len(gvList)) + for i, gv := range gvList { + gvStrList[i] = gv.String() + } + APIKindVersionMap[kind] = gvStrList + } + + return APIKindVersionMap, nil +} + +func (c *ClusterCollector) getAllGVMatchingGroup(groupRegex string, gvList []string) []string { + var filtered []string + + for _, gv := range gvList { + gvObj, err := schema.ParseGroupVersion(gv) + if err != nil { + continue + } + + if gvObj.Group == "" { + continue + } + + re := regexp.MustCompile(groupRegex) + if re.MatchString(gvObj.Group) { + filtered = append(filtered, gv) + } + } + + return filtered +} + +func (c *ClusterCollector) groupOrderPolicy(mapKindGV *map[string][]string) { + globalOrder := c.getGlobalGroupOrder() + for kind, gvList := range *mapKindGV { + sortedGV := []string{} + + //First priority is for known groups + for _, groupKey := range globalOrder { + subsetOfGV := c.getAllGVMatchingGroup(groupKey, gvList) + sortedGV = append(sortedGV, subsetOfGV...) + } + + //Second priority is for unknown groups (which are not empty string) + for _, gv := range gvList { + gvObj, err := schema.ParseGroupVersion(gv) + if err != nil { + continue + } + + if common.IsStringPresent(sortedGV, gv) { + continue + } + + if strings.Compare(gvObj.Group, "") != 0 { + sortedGV = append(sortedGV, gv) + } + } + + //Third priority is for empty groups + for _, gv := range gvList { + gvObj, err := schema.ParseGroupVersion(gv) + if err != nil { + continue + } + + if strings.Compare(gvObj.Group, "") == 0 { + sortedGV = append(sortedGV, gv) + } + } + + if len(sortedGV) > 0 { + (*mapKindGV)[kind] = sortedGV + } else { + (*mapKindGV)[kind] = gvList + } + } +} + +func (c *ClusterCollector) sortVersionList(vList *[]string) { + srcVersionKeys := []string{"alpha", "beta"} + trVersionKeys := []string{"-alpha.", "-beta."} + regex := []string{`\-alpha\.`, `\-beta\.`} + + for index, version := range *vList { + for i, vKey := range srcVersionKeys { + re := regexp.MustCompile(vKey) + if re.MatchString(version) { + //Tranforming the string to the format suitable for semver pkg + (*vList)[index] = re.ReplaceAllString(version, trVersionKeys[i]) + break + } + } + } + + svObjList := make([]*semver.Version, len(*vList)) + for index, versionStr := range *vList { + svObj, err := semver.NewVersion(versionStr) + if err != nil { + log.Warnf("Skipping Version: %s", versionStr) + continue + } + + svObjList[index] = svObj + } + + sort.Sort(sort.Reverse(semver.Collection(svObjList))) + + for index, svObj := range svObjList { + transfVersionStr := svObj.Original() + noMatches := true + for i, vKey := range regex { + re := regexp.MustCompile(vKey) + if re.MatchString(transfVersionStr) { + (*vList)[index] = re.ReplaceAllString(transfVersionStr, srcVersionKeys[i]) + noMatches = false + break + } + } + if noMatches { + (*vList)[index] = transfVersionStr + } + } +} + +func (c *ClusterCollector) clusterByGroupsAndSortVersions(gvList []string) []string { + gvMap := make(map[string][]string) + for _, gvStr := range gvList { + gvObj, err := schema.ParseGroupVersion(gvStr) + if err != nil { + log.Debugf("Error parting group version [%s]", gvStr) + continue + } + + if vList, ok := gvMap[gvObj.Group]; ok { + vList = append(vList, gvObj.Version) + gvMap[gvObj.Group] = vList + } else { + vList = []string{gvObj.Version} + gvMap[gvObj.Group] = vList + } + } + + for _, vList := range gvMap { + c.sortVersionList(&vList) + } + + sortedGVList := []string{} + for group, vList := range gvMap { + for _, v := range vList { + gvObj := schema.GroupVersion{Group: group, Version: v} + sortedGVList = append(sortedGVList, gvObj.String()) + } + } + + return sortedGVList +} + +func (c *ClusterCollector) collectUsingCLI() (map[string][]string, error) { + cmd := exec.Command("bash", "-c", c.getClusterCommand()+" api-resources -o name") + outputStr, err := cmd.Output() + if err != nil { + log.Errorf("Error while running kubectl api-resources: %s", err) + return nil, err + } + log.Debugf("Got kind information for cluster") + nameList := strings.Split(string(outputStr), "\n") + mapKind := make(map[string][]schema.GroupVersion) + for _, name := range nameList { + tmpArray := strings.Split(name, ".") + if len(tmpArray) > 0 { + name := tmpArray[0] + kind, gvStr, err := c.getGVKUsingNameCLI(name) + if err != nil { + log.Debugf("Erroring parsing kind from CLI output") + continue + } + group := "" + for i, tmp := range tmpArray { + if i == 1 { + group = tmp + } else if i > 1 { + tmp = strings.TrimSpace(tmp) + group = group + "." + tmp + } + } + + if group != "" { + if groupArray, ok := mapKind[kind]; ok { + groupArray = append(groupArray, schema.GroupVersion{Group: group, Version: ""}) + mapKind[kind] = groupArray + } else { + mapKind[kind] = []schema.GroupVersion{{Group: group, Version: ""}} + } + } else { + mapKind[kind] = []schema.GroupVersion{{Group: "", Version: gvStr}} + } + } + } + + apiMd := make(map[string][]string) + + for kind, availableGroupList := range mapKind { + if len(availableGroupList) == 1 { + singletonObj := availableGroupList[0] + if strings.Compare(singletonObj.Group, "") == 0 { + apiMd[kind] = []string{singletonObj.Version} + continue + } + } + if len(availableGroupList) > 0 { + gvList := c.getPreferredGVUsingCLI(kind, availableGroupList) + apiMd[kind] = gvList + } else { + log.Warnf("Empty group for kind [%s]", kind) + } + } + + return apiMd, nil +} + +func (c *ClusterCollector) getPreferredGVUsingCLI(kind string, availableGroupList []schema.GroupVersion) []string { + scheme := (&apiresourceset.K8sAPIResourceSet{}).GetScheme() + var gvList []string + for _, gvObj := range availableGroupList { + prioritizedGVList := scheme.PrioritizedVersionsForGroup(gvObj.Group) + if len(prioritizedGVList) > 0 { + for _, gv := range prioritizedGVList { + isSupported, err := c.isSupportedGV(kind, gv.String()) + if isSupported { + gvList = append(gvList, gv.String()) + } else { + log.Debugf("Group version not found by CLI for kind [%s] : %s", kind, err) + } + } + } else { + _, gvStr, err := c.getGVKUsingNameCLI(kind) + if err == nil { + gvList = append(gvList, gvStr) + } + } + } + + return gvList +} + +func (c *ClusterCollector) isSupportedGV(kind string, gvStr string) (bool, error) { + cmd := exec.Command("bash", "-c", c.getClusterCommand()+" explain "+kind+" --api-version="+gvStr+" --recursive") + outputStr, err := cmd.Output() + if err != nil { + log.Debugf("Error while running %s for verifying [%s]\n", c.getClusterCommand(), gvStr) + return false, err + } + + lines := strings.Split(string(outputStr), "\n") + + if len(lines) < 2 { + return false, fmt.Errorf("Description incomplete") + } + + if strings.Contains(lines[1], "VERSION") { + return true, nil + } + + return false, fmt.Errorf("GV [%s] not found", gvStr) +} + +func (c *ClusterCollector) getGVKUsingNameCLI(name string) (string, string, error) { + cmd := exec.Command("bash", "-c", c.getClusterCommand()+" explain "+name) + outputStr, err := cmd.Output() + if err != nil { + //log.Errorf("Error while running kubectl: %s\n", err) + return "", "", err + } + + var gvk schema.GroupVersionKind + + lines := strings.Split(string(outputStr), "\n") + + if len(lines) < 2 { + return "", "", fmt.Errorf("Description incomplete") + } + + if strings.Contains(lines[0], "KIND") { + tmpArray := strings.Split(lines[0], ":") + gvk.Kind = strings.TrimSpace(tmpArray[1]) + } else { + return "", "", err + } + + if strings.Contains(lines[1], "VERSION") { + tmpArray := strings.Split(lines[1], ":") + tmpGV := strings.TrimSpace(tmpArray[1]) + tmpGVLines := strings.Split(tmpGV, "/") + if len(tmpGVLines) == 2 { + gvk.Group = tmpGVLines[0] + gvk.Version = tmpGVLines[1] + } else { + gvk.Group = "" + gvk.Version = tmpGVLines[0] + } + } + + return gvk.Kind, gvk.GroupVersion().String(), nil +} + +//GVExists looks up group version from list +func gvExists(gvList []schema.GroupVersion, gvKey schema.GroupVersion) bool { + for _, gv := range gvList { + if gv.String() == gvKey.String() { + return true + } + } + return false +} diff --git a/internal/collector/collector.go b/internal/collector/collector.go new file mode 100644 index 000000000..b5b6fc211 --- /dev/null +++ b/internal/collector/collector.go @@ -0,0 +1,29 @@ +/* +Copyright IBM Corporation 2020 + +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 collector + +//Collector defines interface for collecting data from data sources +type Collector interface { + Collect(inputDirectory string, outputPath string) error + GetAnnotations() []string +} + +// GetCollectors returns different collectors +func GetCollectors() ([]Collector, error) { + var collectors = []Collector{new(ClusterCollector), new(ImagesCollector), new(CFContainerTypesCollector), new(CfAppsCollector)} + return collectors, nil +} diff --git a/internal/collector/imagescollector.go b/internal/collector/imagescollector.go new file mode 100644 index 000000000..2f7446500 --- /dev/null +++ b/internal/collector/imagescollector.go @@ -0,0 +1,171 @@ +/* +Copyright IBM Corporation 2020 + +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 collector + +import ( + "encoding/json" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" + + sourcetypes "github.com/konveyor/move2kube/internal/collector/sourcetypes" + common "github.com/konveyor/move2kube/internal/common" + collecttypes "github.com/konveyor/move2kube/types/collection" +) + +//ImagesCollector collects the docker images +type ImagesCollector struct { +} + +// GetAnnotations returns annotations on which this collector should be invoked +func (c ImagesCollector) GetAnnotations() []string { + annotations := []string{"k8s", "dockerswarm", "dockercompose"} + return annotations +} + +//Collect gets the image metadata using docker inspect +func (c *ImagesCollector) Collect(inputDirectory string, outputPath string) error { + //Creating the output sub-directory if it does not exist + outputPath = filepath.Join(outputPath, "images") + err := os.MkdirAll(outputPath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create output directory %s : %s", outputPath, err) + return err + } + imageNames, err := getImageNames(inputDirectory) + if err != nil { + return err + } + log.Debugf("Images : %s", imageNames) + for _, imageName := range imageNames { + imagedata, err := getDockerInspectResult(imageName) + if err != nil { + continue + } + if imagedata != nil { + imageInfo := getImageInfo(imagedata) + shortesttag := "" + for _, tag := range imageInfo.Spec.Tags { + if shortesttag == "" { + shortesttag = tag + } else { + if len(shortesttag) > len(tag) { + shortesttag = tag + } + } + } + imagefile := filepath.Join(outputPath, common.NormalizeForFilename(shortesttag)+".yaml") + err := common.WriteYaml(imagefile, imageInfo) + log.Errorf("Unable to write file %s : %s", imagefile, err) + } + } + + return nil +} + +func getDockerInspectResult(imageName string) ([]byte, error) { + cmd := exec.Command("docker", "inspect", imageName) + jsonOutput, err := cmd.CombinedOutput() + if err != nil { + if strings.Contains(string(jsonOutput), "permission denied") { + log.Warnf("Error while running docker-inspect due to lack of permissions") + log.Warnf("Please refer to [https://docs.docker.com/engine/install/linux-postinstall/] to fix this issue") + } else if strings.Contains(string(jsonOutput), "No such object") { + log.Warnf("Image [%s] not available in local image repo. Run \"docker pull %s\"", imageName, imageName) + return nil, nil + } else { + log.Warnf("Error while running docker-inspect: %s", err) + } + return nil, err + } + return jsonOutput, nil +} + +func getImageInfo(data []byte) collecttypes.ImageInfo { + imageInfo := collecttypes.NewImageInfo() + imgLayerInfo := make([]sourcetypes.DockerImage, 0) + err := json.Unmarshal(data, &imgLayerInfo) + if err != nil { + log.Errorf("Unable to unmarshal image info : %s", err) + } + for _, image := range imgLayerInfo { + imageInfo.Spec.Tags = image.RepoTags + imageInfo.Spec.UserID, err = strconv.Atoi(image.CConfig.User) + if err != nil { + log.Debugf("UserID not available in image metadata for [%s]", image.RepoTags[0]) + imageInfo.Spec.UserID = -1 + } + imageInfo.Spec.AccessedDirs = append(imageInfo.Spec.AccessedDirs, image.CConfig.WorkingDir) + for key := range image.CConfig.EPorts { + regex := regexp.MustCompile("[0-9]+") + portNumber, err := strconv.Atoi(string(regex.FindAll([]byte(key), -1)[0])) + if err != nil { + log.Debugf("PortNumber not available in image metadata for [%s]", image.RepoTags[0]) + } else { + imageInfo.Spec.PortsToExpose = append(imageInfo.Spec.PortsToExpose, portNumber) + } + } + } + return imageInfo +} + +func getImageNames(inputPath string) ([]string, error) { + if inputPath == "" { + return getAllImageNames() + } + return getDCImageNames(inputPath) +} + +func getAllImageNames() ([]string, error) { + cmd := exec.Command("bash", "-c", "docker image list --format '{{.Repository}}:{{.Tag}}'") + outputStr, err := cmd.Output() + if err != nil { + log.Warnf("Error while running docker image list : %s", err) + return nil, err + } + images := strings.Split(string(outputStr), "\n") + cleanimages := []string{} + for _, image := range images { + if strings.HasPrefix(image, "") || strings.HasSuffix(image, "") { + log.Debugf("Ignore image with : %s", image) + continue + } + } + return cleanimages, err +} + +func getDCImageNames(directorypath string) ([]string, error) { + var imageNames []string + files, err := common.GetFilesByExt(directorypath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize Docker image yamls : %s", err) + } + for _, path := range files { + dc := new(sourcetypes.DockerCompose) + if common.ReadYaml(path, &dc) == nil { + for _, dcservice := range dc.DCServices { + imageNames = append(imageNames, dcservice.Image) + } + } + } + return imageNames, nil +} diff --git a/internal/collector/sourcetypes/cfinstanceapps.go b/internal/collector/sourcetypes/cfinstanceapps.go new file mode 100644 index 000000000..31115e1db --- /dev/null +++ b/internal/collector/sourcetypes/cfinstanceapps.go @@ -0,0 +1,39 @@ +/* +Copyright IBM Corporation 2020 + +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 sourcetypes + +// CfInstanceApps for reading cf running instance metadata +type CfInstanceApps struct { + CfResources []CfResource `json:"resources"` +} + +// CfResource reads entity +type CfResource struct { + CfAppEntity CfSourceApplication `json:"entity"` +} + +// CfSourceApplication reads source application +type CfSourceApplication struct { + Name string `json:"name"` + Buildpack string `json:"buildpack"` + DetectedBuildpack string `json:"detected_buildpack"` + Memory int64 `json:"memory"` + Instances int `json:"instances"` + DockerImage string `json:"dockerimage"` + Ports []int32 `json:"ports"` + Env map[string]string `json:"environment_json,omitempty"` +} diff --git a/internal/collector/sourcetypes/dockercompose.go b/internal/collector/sourcetypes/dockercompose.go new file mode 100644 index 000000000..5752ccff0 --- /dev/null +++ b/internal/collector/sourcetypes/dockercompose.go @@ -0,0 +1,28 @@ +/* +Copyright IBM Corporation 2020 + +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 sourcetypes + +// DockerCompose reads docker compose files +type DockerCompose struct { + Version string `yaml:"version"` + DCServices map[string]DCService `yaml:"services"` +} + +// DCService reads service +type DCService struct { + Image string `yaml:"image,omitempty"` +} diff --git a/internal/collector/sourcetypes/dockerinspect.go b/internal/collector/sourcetypes/dockerinspect.go new file mode 100644 index 000000000..f12189cc9 --- /dev/null +++ b/internal/collector/sourcetypes/dockerinspect.go @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2020 + +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 sourcetypes + +// DockerImage loads docker image +type DockerImage struct { + RepoTags []string `json:"RepoTags"` + CConfig ContainerConfig `json:"ContainerConfig"` +} + +// ContainerConfig loads container config +type ContainerConfig struct { + EPorts map[string]interface{} `json:"ExposedPorts"` + User string `json:"User"` + Env []string `json:"Env"` + WorkingDir string `json:"WorkingDir"` +} diff --git a/internal/common/constants.go b/internal/common/constants.go new file mode 100644 index 000000000..270aa650f --- /dev/null +++ b/internal/common/constants.go @@ -0,0 +1,67 @@ +/* +Copyright IBM Corporation 2020 + +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 common + +import ( + "os" + "path/filepath" + + "github.com/konveyor/move2kube/types" + "k8s.io/apimachinery/pkg/api/resource" +) + +const ( + // DefaultProjectName represents the short app name + DefaultProjectName string = "myproject" + // DefaultPlanFile defines default name for plan file + DefaultPlanFile string = types.AppNameShort + ".plan" + // TempDirPrefix defines the prefix of the temp directory + TempDirPrefix string = types.AppNameShort + "-" + // AssetsDir defines the dir of the assets temp directory + AssetsDir string = types.AppNameShort + "assets" + // VolumePrefix defines the prefix to be used for volumes + VolumePrefix string = "vol" + // DefaultStorageClassName defines the default storage class to be used + DefaultStorageClassName string = "default" + // DefaultDirectoryPermission defines the default permission used when a directory is created + DefaultDirectoryPermission os.FileMode = 0755 + // DefaultExecutablePermission defines the default permission used when an executable file is created + DefaultExecutablePermission os.FileMode = 0744 + // DefaultFilePermission defines the default permission used when a non-executable file is created + DefaultFilePermission os.FileMode = 0644 + // DefaultRegistryURL points to the default registry url that will be used + DefaultRegistryURL string = "docker.io" + // ImagePullSecretPrefix is the prefix that will be prepended to pull secret name + ImagePullSecretPrefix string = "imagepullsecret" + // QACacheFile defines the location of the QA cache file + QACacheFile string = types.AppNameShort + "qacache.yaml" + // DefaultClusterType defines the default cluster type chosen by plan + DefaultClusterType string = "Kubernetes" + // IgnoreFilename is the name of the file containing the ignore rules and exceptions + IgnoreFilename string = "." + types.AppNameShort + "ignore" +) + +var ( + // DefaultPVCSize stores the default PVC size + DefaultPVCSize, _ = resource.ParseQuantity("100Mi") + // IgnoreEnvironment indicates whether to ignore the current environment or not + IgnoreEnvironment = false + // TempPath defines where all app data get stored during execution + TempPath = TempDirPrefix + "temp" + // AssetsPath defines where all assets get stored during execution + AssetsPath = filepath.Join(TempPath, AssetsDir) +) diff --git a/internal/common/generator/generator.go b/internal/common/generator/generator.go new file mode 100644 index 000000000..d318258a0 --- /dev/null +++ b/internal/common/generator/generator.go @@ -0,0 +1,211 @@ +// +build !excludecodegen + +/* +Copyright IBM Corporation 2020 + +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 program generates dockerfiles.go. It can be invoked by running +// go generate +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "github.com/konveyor/move2kube/internal/common" + log "github.com/sirupsen/logrus" +) + +// Reads all non .go files in the current directory +// and encodes them as strings literals in .go + +const ( + license = `/* +Copyright IBM Corporation 2020 + +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. +*/` + + conststemp = `// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// {{ .Timestamp }} + +` + license + ` + +package {{ .Directory }} + +const ( +{{ range $file, $contents := .Files }} + {{ $file }} = ` + "`{{ $contents }}`" + ` +{{ end }} +)` + + maptemp = `// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// {{ .Timestamp }} + +` + license + ` + +package {{ .Directory }} + +var Constants= map[string]string{ +{{ range $file, $contents := .Files }} + ` + "`{{ $file }}`" + ` : ` + "`{{ $contents }}`," + ` +{{ end }} +}` + + tartemp = `// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// {{ .Timestamp }} + +` + license + ` + +package {{ .Directory }} + +const Tar = ` + "`{{ .TarString }}`" +) + +//TODO: Fix cases where the file has ` in contents +func main() { + directory := os.Args[1] + if len(os.Args) == 3 { + if os.Args[2] == "makemaps" { + if err := makeConstants(directory, maptemp); err != nil { + log.Fatalf("Error during code gen on directory %q with makemaps Error: %q", directory, err) + } + } else if os.Args[2] == "maketar" { + if err := makeTar(directory); err != nil { + log.Fatalf("Error during code gen on directory %q with maketar Error: %q", directory, err) + } + } + } else { + if err := makeConstants(directory, conststemp); err != nil { + log.Fatalf("Error during code gen on directory %q with consts template Error: %q", directory, err) + } + } +} + +func makeConstants(directory string, tplstr string) error { + files := make(map[string]string) + + log.Infof("Generating constants from %q", directory) + fs, err := ioutil.ReadDir(directory) + if err != nil { + log.Errorf("Failed to read the directory %q Error: %q", directory, err) + return err + } + for _, f := range fs { + file := f.Name() + if f.IsDir() { + continue + } + if !strings.HasSuffix(file, ".go") && !strings.HasPrefix(file, ".") { + currpath := filepath.Join(directory, file) + content, err := ioutil.ReadFile(currpath) + if err != nil { + log.Errorf("Failed to read the file at path %q Error: %q", currpath, err) + return err + } + // Convert []byte to string + files[strings.ReplaceAll(file, ".", "_")] = string(content) + } + } + + outputpath := filepath.Join(directory, "constants.go") + f, err := os.Create(outputpath) + if err != nil { + log.Errorf("Failed to create the file at path %q Error: %q", outputpath, err) + return err + } + defer f.Close() + + var tpl = template.Must(template.New("").Parse(tplstr)) + + absdirectory, err := filepath.Abs(directory) + if err != nil { + log.Errorf("Unable to resolve full path of directory %q : %q", absdirectory, err) + return err + } + + err = tpl.Execute(f, struct { + Timestamp time.Time + Directory string + Files map[string]string + }{ + Timestamp: time.Now(), + Directory: filepath.Base(absdirectory), + Files: files, + }) + if err != nil { + log.Errorf("Failed to execute the template. Error: %q", err) + return err + } + return nil +} + +func makeTar(directory string) error { + outputpath := filepath.Join(directory, "constants.go") + f, err := os.Create(outputpath) + if err != nil { + log.Errorf("Failed to create the file at path %q Error: %q", outputpath, err) + return err + } + defer f.Close() + + log.Infof("Generating tar from %q", directory) + + tarString, err := common.TarAsString(directory, []string{"constants.go", "assets.go"}) + if err != nil { + log.Errorf("Error while creating tar : %q", err) + return err + } + + var tpl = template.Must(template.New("").Parse(tartemp)) + + absdirectory, err := filepath.Abs(directory) + if err != nil { + log.Errorf("Unable to resolve full path of directory %q : %q", absdirectory, err) + return err + } + + err = tpl.Execute(f, struct { + Timestamp time.Time + Directory string + TarString string + }{ + Timestamp: time.Now(), + Directory: filepath.Base(absdirectory), + TarString: tarString, + }) + if err != nil { + log.Errorf("Failed to execute the template. Error: %q", err) + return err + } + return nil +} diff --git a/internal/common/generator/generator_test.go b/internal/common/generator/generator_test.go new file mode 100644 index 000000000..2c59d01bd --- /dev/null +++ b/internal/common/generator/generator_test.go @@ -0,0 +1,274 @@ +// +build !excludecodegen + +/* +Copyright IBM Corporation 2020 + +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 ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestMakeConstants(t *testing.T) { + t.Run("try to generate code for non existent directory", func(t *testing.T) { + path := "testdata/nonexistent" + if err := makeConstants(path, maptemp); err == nil { + t.Fatalf("Should have failed since the directory %q does not exist.", path) + } + }) + + t.Run("read empty directory and generate code with maptemp", func(t *testing.T) { + // Setup + testparentdir := t.TempDir() + testdir := filepath.Join(testparentdir, "foobar") + if err := os.Mkdir(testdir, os.ModePerm); err != nil { + t.Fatalf("Failed to create the test directory at path %q. Error: %q", testdir, err) + } + fpath := filepath.Join(testdir, "constants.go") + testdatapath := "testdata/maptempemptyskiptimestamp.txt" + testdatabytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q. Error: %q", testdatapath, err) + } + want := string(testdatabytes) + wantNumLines := 8 + 17 // 17 lines for license and spaces + + // Test + if err := makeConstants(testdir, maptemp); err != nil { + t.Fatalf("Failed to generate the code for directory %q with maps template Error: %q", testdir, err) + } + databytes, err := ioutil.ReadFile(fpath) + if err != nil { + t.Fatal("Failed to create the constants.go file (or failed to read it after creation). Error:", err) + } + data := string(databytes) + lines := strings.Split(data, "\n") + if len(lines) != wantNumLines { + t.Fatal("Failed to generate the code properly. Expected number of lines:", wantNumLines, "Actual:", len(lines)) + } + lines = append(lines[:2], lines[3:]...) // Skip the timestamp + data = strings.Join(lines, "\n") + if data != want { + t.Fatal("Failed to generate the code properly. Expected:", want, "Actual:", data) + } + }) + + t.Run("read empty directory and generate code with conststemp", func(t *testing.T) { + // Setup + testparentdir := t.TempDir() + testdir := filepath.Join(testparentdir, "foobar") + if err := os.Mkdir(testdir, os.ModePerm); err != nil { + t.Fatalf("Failed to create the test directory at path %q. Error: %q", testdir, err) + } + fpath := filepath.Join(testdir, "constants.go") + testdatapath := "testdata/conststempemptyskiptimestamp.txt" + testdatabytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q. Error: %q", testdatapath, err) + } + want := string(testdatabytes) + wantNumLines := 8 + 17 // 17 lines for license and spaces + + // Test + if err := makeConstants(testdir, conststemp); err != nil { + t.Fatalf("Failed to generate the code for directory %q with consts template Error: %q", testdir, err) + } + databytes, err := ioutil.ReadFile(fpath) + if err != nil { + t.Fatal("Failed to create the constants.go file (or failed to read it after creation). Error:", err) + } + data := string(databytes) + lines := strings.Split(data, "\n") + if len(lines) != wantNumLines { + t.Fatal("Failed to generate the code properly. Expected number of lines:", wantNumLines, "Actual:", len(lines)) + } + lines = append(lines[:2], lines[3:]...) // Skip the timestamp + data = strings.Join(lines, "\n") + if data != want { + t.Fatal("Failed to generate the code properly. Expected:", want, "Actual:", data) + } + }) + + t.Run("read filled directory and generate code with maptemp", func(t *testing.T) { + // Setup + testdir := "testdata/datafortempfilled" + fpath := filepath.Join(testdir, "constants.go") + // Remove the constants.go file if it exists from previous runs. + if err := os.Remove(fpath); err != nil && !os.IsNotExist(err) { + t.Fatalf("Failed to remove the file at path %q. Error: %q", fpath, err) + } + testdatapath := "testdata/maptempfilledskiptimestamp.txt" + testdatabytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q. Error: %q", testdatapath, err) + } + want := string(testdatabytes) + wantNumLines := 22 + 17 // 17 lines for license and spaces + + // Test + if err := makeConstants(testdir, maptemp); err != nil { + t.Fatalf("Failed to generate the code for directory %q with maps template Error: %q", testdir, err) + } + defer os.Remove(fpath) + databytes, err := ioutil.ReadFile(fpath) + if err != nil { + t.Fatal("Failed to create the constants.go file (or failed to read it after creation). Error:", err) + } + data := string(databytes) + lines := strings.Split(data, "\n") + if len(lines) != wantNumLines { + t.Fatal("Failed to generate the code properly. Expected number of lines:", wantNumLines, "Actual:", len(lines)) + } + lines = append(lines[:2], lines[3:]...) // Skip the timestamp + data = strings.Join(lines, "\n") + if data != want { + t.Fatal("Failed to generate the code properly. Expected:", want, "Actual:", data) + } + }) + + t.Run("read filled directory and generate code with conststemp", func(t *testing.T) { + // Setup + testdir := "testdata/datafortempfilled" + fpath := filepath.Join(testdir, "constants.go") + // Remove the constants.go file if it exists from previous runs. + if err := os.Remove(fpath); err != nil && !os.IsNotExist(err) { + t.Fatalf("Failed to remove the file at path %q. Error: %q", fpath, err) + } + testdatapath := "testdata/conststempfilledskiptimestamp.txt" + testdatabytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q. Error: %q", testdatapath, err) + } + want := string(testdatabytes) + wantNumLines := 22 + 17 // 17 lines for license and spaces + + // Test + if err := makeConstants(testdir, conststemp); err != nil { + t.Fatalf("Failed to generate the code for directory %q with consts template Error: %q", testdir, err) + } + defer os.Remove(fpath) + databytes, err := ioutil.ReadFile(fpath) + if err != nil { + t.Fatal("Failed to create the constants.go file (or failed to read it after creation). Error:", err) + } + data := string(databytes) + lines := strings.Split(data, "\n") + if len(lines) != wantNumLines { + t.Fatal("Failed to generate the code properly. Expected number of lines:", wantNumLines, "Actual:", len(lines)) + } + lines = append(lines[:2], lines[3:]...) // Skip the timestamp + data = strings.Join(lines, "\n") + if data != want { + t.Fatal("Failed to generate the code properly. Expected:", want, "Actual:", data) + } + }) + + t.Run("generate code from directory containing files that we have no permissions to read", func(t *testing.T) { + // Setup + testdir := t.TempDir() + fpath := filepath.Join(testdir, "foobar") + if err := ioutil.WriteFile(fpath, []byte("no permission to read this file"), 0); err != nil { + t.Fatalf("Failed to create the temporary file %q for testing.", fpath) + } + + // Test + if err := makeConstants(testdir, conststemp); err == nil { + t.Fatalf("Should not have succeeded since the directory contains a file %q we don't have permissions to read.", fpath) + } + }) + + t.Run("generate code from directory that we have no permissions to write to", func(t *testing.T) { + // Setup + tempdir := t.TempDir() + testdir := filepath.Join(tempdir, "foobar") + if err := os.Mkdir(testdir, 0400); err != nil { + t.Fatalf("Failed to create the temporary directory %q for testing. Error: %q", testdir, err) + } + + // Test + if err := makeConstants(testdir, conststemp); err == nil { + t.Fatalf("Should not have succeeded since we don't have permissions to write into the directory %q", testdir) + } + }) +} + +func TestMakeTar(t *testing.T) { + t.Run("make a tar using a filled directory", func(t *testing.T) { + // Setup + testdir := "testdata/datafortempfilled" + fpath := filepath.Join(testdir, "constants.go") + // Remove the constants.go file if it exists from previous runs. + if err := os.Remove(fpath); err != nil && !os.IsNotExist(err) { + t.Fatalf("Failed to remove the file at path %q. Error: %q", fpath, err) + } + testdatapath := "testdata/tartempfilledskiptimestampandtar.txt" + testdatabytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q. Error: %q", testdatapath, err) + } + want := string(testdatabytes) + wantNumLines := 6 + 17 // 17 lines for license and spaces + + // Test + if err := makeTar(testdir); err != nil { + t.Fatalf("Failed to generate the code for directory %q with tar template Error: %q", testdir, err) + } + defer os.Remove(fpath) + databytes, err := ioutil.ReadFile(fpath) + if err != nil { + t.Fatal("Failed to create the constants.go file (or failed to read it after creation). Error:", err) + } + data := string(databytes) + lines := strings.Split(data, "\n") + if len(lines) != wantNumLines { + t.Fatal("Failed to generate the code properly. Expected number of lines:", wantNumLines, "Actual:", len(lines)) + } + lines = append(lines[:2], lines[3:len(lines)-1]...) // Skip the timestamp and the last line since the tar string also has a timestamp + data = strings.Join(lines, "\n") + if data != want { + t.Fatal("Failed to generate the code properly. Expected:", want, "Actual:", data) + } + }) + + t.Run("make a tar when the directory has files which we have no permissions to read", func(t *testing.T) { + tempdir := t.TempDir() + fpath := filepath.Join(tempdir, "nopermstoread") + if err := ioutil.WriteFile(fpath, []byte("no permission to read this file"), 0); err != nil { + t.Fatalf("Failed to create the temporary file %q for testing.", fpath) + } + if err := makeTar(tempdir); err == nil { + t.Fatalf("Should not have succeeded since the directory contains a file %q we don't have permissions to read.", fpath) + } + }) + + t.Run("make a tar in a directory that we have no permissions to write to", func(t *testing.T) { + // Setup + tempdir := t.TempDir() + testdir := filepath.Join(tempdir, "foobar") + if err := os.Mkdir(testdir, 0400); err != nil { + t.Fatalf("Failed to create the temporary directory %q for testing. Error: %q", testdir, err) + } + + // Test + if err := makeTar(testdir); err == nil { + t.Fatalf("Should not have succeeded since we don't have permissions to write into the directory %q", testdir) + } + }) +} diff --git a/internal/common/generator/testdata/conststempemptyskiptimestamp.txt b/internal/common/generator/testdata/conststempemptyskiptimestamp.txt new file mode 100644 index 000000000..a0c4e43a8 --- /dev/null +++ b/internal/common/generator/testdata/conststempemptyskiptimestamp.txt @@ -0,0 +1,24 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at + +/* +Copyright IBM Corporation 2020 + +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 foobar + +const ( + +) \ No newline at end of file diff --git a/internal/common/generator/testdata/conststempfilledskiptimestamp.txt b/internal/common/generator/testdata/conststempfilledskiptimestamp.txt new file mode 100644 index 000000000..24e03f9d2 --- /dev/null +++ b/internal/common/generator/testdata/conststempfilledskiptimestamp.txt @@ -0,0 +1,38 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at + +/* +Copyright IBM Corporation 2020 + +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 datafortempfilled + +const ( + + test1_json = `{ + "foo": "bar", + "id": 42, + "key1": ["val1", "val2", "val3"] +}` + + test2_yml = `--- +key1: + subkey1: subval1 + subkey2: + - subval2arr1 + - subval2arr2 +...` + +) \ No newline at end of file diff --git a/internal/common/generator/testdata/datafortempfilled/test1.json b/internal/common/generator/testdata/datafortempfilled/test1.json new file mode 100644 index 000000000..44e2ea174 --- /dev/null +++ b/internal/common/generator/testdata/datafortempfilled/test1.json @@ -0,0 +1,5 @@ +{ + "foo": "bar", + "id": 42, + "key1": ["val1", "val2", "val3"] +} \ No newline at end of file diff --git a/internal/common/generator/testdata/datafortempfilled/test2.yml b/internal/common/generator/testdata/datafortempfilled/test2.yml new file mode 100644 index 000000000..4fe77ac27 --- /dev/null +++ b/internal/common/generator/testdata/datafortempfilled/test2.yml @@ -0,0 +1,7 @@ +--- +key1: + subkey1: subval1 + subkey2: + - subval2arr1 + - subval2arr2 +... \ No newline at end of file diff --git a/internal/common/generator/testdata/datafortempfilled/testconfigs/test3.yml b/internal/common/generator/testdata/datafortempfilled/testconfigs/test3.yml new file mode 100644 index 000000000..c5d0fb977 --- /dev/null +++ b/internal/common/generator/testdata/datafortempfilled/testconfigs/test3.yml @@ -0,0 +1,7 @@ +--- +key1: + subkey1: foo + subkey2: + - bar + - baz +... \ No newline at end of file diff --git a/internal/common/generator/testdata/maptempemptyskiptimestamp.txt b/internal/common/generator/testdata/maptempemptyskiptimestamp.txt new file mode 100644 index 000000000..5eede2739 --- /dev/null +++ b/internal/common/generator/testdata/maptempemptyskiptimestamp.txt @@ -0,0 +1,24 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at + +/* +Copyright IBM Corporation 2020 + +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 foobar + +var Constants= map[string]string{ + +} \ No newline at end of file diff --git a/internal/common/generator/testdata/maptempfilledskiptimestamp.txt b/internal/common/generator/testdata/maptempfilledskiptimestamp.txt new file mode 100644 index 000000000..150ca6aa6 --- /dev/null +++ b/internal/common/generator/testdata/maptempfilledskiptimestamp.txt @@ -0,0 +1,38 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at + +/* +Copyright IBM Corporation 2020 + +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 datafortempfilled + +var Constants= map[string]string{ + + `test1_json` : `{ + "foo": "bar", + "id": 42, + "key1": ["val1", "val2", "val3"] +}`, + + `test2_yml` : `--- +key1: + subkey1: subval1 + subkey2: + - subval2arr1 + - subval2arr2 +...`, + +} \ No newline at end of file diff --git a/internal/common/generator/testdata/tartempfilledskiptimestampandtar.txt b/internal/common/generator/testdata/tartempfilledskiptimestampandtar.txt new file mode 100644 index 000000000..448492a9e --- /dev/null +++ b/internal/common/generator/testdata/tartempfilledskiptimestampandtar.txt @@ -0,0 +1,20 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at + +/* +Copyright IBM Corporation 2020 + +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 datafortempfilled diff --git a/internal/common/tar.go b/internal/common/tar.go new file mode 100644 index 000000000..fef4b1239 --- /dev/null +++ b/internal/common/tar.go @@ -0,0 +1,116 @@ +/* +Copyright IBM Corporation 2020 + +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 common + +import ( + "archive/tar" + "bytes" + "encoding/base64" + "fmt" + "io" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" +) + +// TarAsString converts a directory into a string +func TarAsString(path string, ignorefiles []string) (string, error) { + + buf := bytes.NewBuffer([]byte{}) + tw := tar.NewWriter(buf) + defer tw.Close() + + err := filepath.Walk(path, func(currpath string, finfo os.FileInfo, err error) error { + if err != nil { + return err + } + hdr, err := tar.FileInfoHeader(finfo, finfo.Name()) + if err != nil { + return err + } + if hdr.Name, err = filepath.Rel(path, currpath); err != nil { + return err + } + for _, ignorefile := range ignorefiles { + if hdr.Name == ignorefile { + return nil + } + } + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if finfo.Mode().IsDir() { + return nil + } + currfile, err := os.Open(currpath) + if err != nil { + return err + } + defer currfile.Close() + _, err = io.Copy(tw, currfile) + if err != nil { + return err + } + return nil + }) + if err != nil { + log.Warnf("Failed to create tar string: %s : %s", path, err) + } + return base64.StdEncoding.EncodeToString(buf.Bytes()), err +} + +// UnTarString converts a string into a directory +func UnTarString(tarstring string, path string) (err error) { + val, err := base64.StdEncoding.DecodeString(tarstring) + if err != nil { + log.Errorf("Unable to decode tarstring : %s", err) + return err + } + tr := tar.NewReader(bytes.NewReader(val)) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + finfo := hdr.FileInfo() + fileName := hdr.Name + filepath := filepath.Join(path, fileName) + if finfo.Mode().IsDir() { + if err := os.MkdirAll(filepath, DefaultDirectoryPermission); err != nil { + return err + } + continue + } + file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, finfo.Mode().Perm()) + if err != nil { + return err + } + defer file.Close() + size, err := io.Copy(file, tr) + if err != nil { + return err + } + if size != finfo.Size() { + return fmt.Errorf("Size mismatch: Wrote %d, Expected %d", size, finfo.Size()) + } + } + return nil +} diff --git a/internal/common/tar_test.go b/internal/common/tar_test.go new file mode 100644 index 000000000..df5549cbe --- /dev/null +++ b/internal/common/tar_test.go @@ -0,0 +1,277 @@ +/* +Copyright IBM Corporation 2020 + +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 common_test + +import ( + "archive/tar" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/konveyor/move2kube/internal/common" + log "github.com/sirupsen/logrus" +) + +// myFileInfo is used to hold the part of the expected outputs for each testcase +// This is only for testing purposes. +type myFileInfo struct { + Name string + Size int64 + Mode os.FileMode + IsDir bool +} + +func newMyFileInfo(f os.FileInfo) myFileInfo { + return myFileInfo{f.Name(), f.Size(), f.Mode(), f.IsDir()} +} + +// myUnTarForTesting extracts information from a tar string. +// This function is for testing purposes only. +func myUnTarForTesting(tarstring string, myfinfos []myFileInfo, contents []string) error { + val, err := base64.StdEncoding.DecodeString(tarstring) + if err != nil { + log.Errorf("Unable to decode tarstring : %s", err) + } + tr := tar.NewReader(bytes.NewReader(val)) + expectedTotal := len(myfinfos) + + for i := range myfinfos { + hdr, err := tr.Next() + if err == io.EOF { + return fmt.Errorf("The tar string %q contains less files than expected. Expected: %d Actual: %d", tarstring, expectedTotal, i) + } + if err != nil { + return err + } + finfo := hdr.FileInfo() + actualmyfinfo := newMyFileInfo(finfo) + if actualmyfinfo != myfinfos[i] { + return fmt.Errorf("The info for file number %d is incorrect. Expected: %v Actual %v", i, myfinfos[i], actualmyfinfo) + } + if finfo.Mode().IsDir() { + continue + } + buf := bytes.NewBuffer([]byte{}) + size, err := io.Copy(buf, tr) + if err != nil { + return err + } + if size != finfo.Size() { + return fmt.Errorf("Size mismatch: Wrote %d, Expected %d", size, finfo.Size()) + } + actualContents := buf.String() + if actualContents != contents[i] { + return fmt.Errorf("The contents of the file at path %q is incorrect. Expected: %q Actual: %q", actualmyfinfo.Name, contents[i], actualContents) + } + } + if _, err := tr.Next(); err != io.EOF { + return fmt.Errorf("The tar string %q contains more files than expected. Expected: %d Actual: at least %d", tarstring, expectedTotal, expectedTotal+1) + } + return nil +} + +func TestTarAsString(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("tar as string when the path doesn't exist", func(t *testing.T) { + if _, err := common.TarAsString("foobar", []string{}); err == nil { + t.Fatal("Should not have succeeded since the path doesn't exist.") + } + }) + + t.Run("tar as string when the path is a file", func(t *testing.T) { + path1 := "testdata/datafortestingtar/tobetarred/test1.yaml" + // want := "" + expectedInfos := []myFileInfo{ + {".", 217, 0644, false}, + } + data, err := ioutil.ReadFile(path1) + if err != nil { + t.Fatalf("Failed to read the test data at %q. Error %q", path1, err) + } + expectedContents := []string{ + string(data), + } + if tarstring, err := common.TarAsString(path1, []string{}); err != nil { + t.Fatalf("Failed to tar the file %q. Error: %q", path1, err) + } else if err := myUnTarForTesting(tarstring, expectedInfos, expectedContents); err != nil { + t.Fatal("Failed to tar the file", path1, "properly. Error:", err) + } + }) + + t.Run("tar as string when the path is an empty directory", func(t *testing.T) { + parent := t.TempDir() + path1 := filepath.Join(parent, "foobar") + if err := os.Mkdir(path1, os.ModePerm); err != nil { + t.Fatal("Failed to make the directory", path1, "Error:", err) + } + // want := "" + expectedInfos := []myFileInfo{ + {".", 0, 0o20000000755, true}, + } + expectedContents := []string{} + if tarstring, err := common.TarAsString(path1, []string{}); err != nil { + t.Fatalf("Failed to tar the file %q. Error: %q", path1, err) + } else if err := myUnTarForTesting(tarstring, expectedInfos, expectedContents); err != nil { + t.Fatal("Failed to tar the file", path1, "properly. Error:", err) + } + }) + + t.Run("tar as string when the path is a filled directory", func(t *testing.T) { + testdirpath := "testdata/datafortestingtar/tobetarred" + // want := "" + expectedInfos := []myFileInfo{ + {".", 0, 0o20000000755, true}, + } + expectedContents := []string{ + "", + } + finfos, err := ioutil.ReadDir(testdirpath) + if err != nil { + t.Fatalf("Failed to read the testdata at path: %q", testdirpath) + } + for _, finfo := range finfos { + expectedInfos = append(expectedInfos, newMyFileInfo(finfo)) + testfilepath := filepath.Join(testdirpath, finfo.Name()) + testfilecontents, err := ioutil.ReadFile(testfilepath) + if err != nil { + t.Fatalf("Failed to read the testdata at path: %q", testfilepath) + } + expectedContents = append(expectedContents, string(testfilecontents)) + } + if tarstring, err := common.TarAsString(testdirpath, []string{}); err != nil { + t.Fatalf("Failed to tar the file %q. Error: %q", testdirpath, err) + } else if err := myUnTarForTesting(tarstring, expectedInfos, expectedContents); err != nil { + t.Fatal("Failed to tar the file", testdirpath, "properly. Error:", err) + } + }) + + t.Run("tar as string while ignoring some files", func(t *testing.T) { + testdirpath := "testdata/datafortestingtar/tobetarred" + ignoredFiles := []string{"test2.yml", "versioninfo.json", "foobar.json"} + // want := "" + expectedInfos := []myFileInfo{ + {".", 0, 0o20000000755, true}, + } + expectedContents := []string{ + "", + } + finfos, err := ioutil.ReadDir(testdirpath) + if err != nil { + t.Fatalf("Failed to read the testdata at path: %q", testdirpath) + } + for _, finfo := range finfos { + if common.IsStringPresent(ignoredFiles, finfo.Name()) { + continue + } + expectedInfos = append(expectedInfos, newMyFileInfo(finfo)) + testfilepath := filepath.Join(testdirpath, finfo.Name()) + testfilecontents, err := ioutil.ReadFile(testfilepath) + if err != nil { + t.Fatalf("Failed to read the testdata at path: %q", testfilepath) + } + expectedContents = append(expectedContents, string(testfilecontents)) + } + if tarstring, err := common.TarAsString(testdirpath, ignoredFiles); err != nil { + t.Fatalf("Failed to tar the file %q. Error: %q", testdirpath, err) + } else if err := myUnTarForTesting(tarstring, expectedInfos, expectedContents); err != nil { + t.Fatal("Failed to tar the file", testdirpath, "properly. Error:", err) + } + }) + + t.Run("tar as string when the directory has files which you don't have permission to read", func(t *testing.T) { + path1 := t.TempDir() + path2 := filepath.Join(path1, "nopermstoread") + if err := ioutil.WriteFile(path2, []byte("no permission to read this file"), 0); err != nil { + t.Fatalf("Failed to create the temporary file %q for testing.", path2) + } + if _, err := common.TarAsString(path2, []string{}); err == nil { + t.Fatalf("Should not have succeeded since the directory contains a file %q we don't have permissions to read.", path2) + } + }) +} + +func TestUnTarString(t *testing.T) { + log.SetLevel(log.DebugLevel) + + testdatadir := "testdata/datafortestingtar/untarstring.json" + testdata := map[string]string{} + if testdatabytes, err := ioutil.ReadFile(testdatadir); err != nil { + t.Fatal("Failed to read the testdata at path:", testdatadir, "Error:", err) + } else if err := json.Unmarshal(testdatabytes, &testdata); err != nil { + t.Fatal("Failed to unmarshal the json testdata at path:", testdatadir, "Error:", err) + } + + t.Run("untar a valid tarstring", func(t *testing.T) { + path := t.TempDir() + tarstring := testdata["untar_a_valid_tarstring"] + if err := common.UnTarString(tarstring, path); err != nil { + t.Fatalf("Failed to untar the tarstring %q to the path %q. Error: %q", tarstring, path, err) + } + if _, err := common.TarAsString(path, []string{}); err != nil { + t.Fatalf("Failed to tar as string the directory %q we just untarred. Error: %q", path, err) + } + }) + + t.Run("untar an invalid base 64 string", func(t *testing.T) { + path := t.TempDir() + tarstring := testdata["untar_an_invalid_base_64_string"] + if err := common.UnTarString(tarstring, path); err == nil { + t.Fatalf("Should have given an error since the tarstring %q is not a valid base 64 string.", tarstring) + } + }) + + t.Run("untar an invalid tarstring", func(t *testing.T) { + path := t.TempDir() + tarstring := testdata["untar_an_invalid_tarstring"] + if err := common.UnTarString(tarstring, path); err == nil { + t.Fatalf("Should have given an error since the tarstring %q is not valid.", tarstring) + } + }) + + t.Run("untar into a directory we don't have permission to write to", func(t *testing.T) { + parent := t.TempDir() + noPermDir := filepath.Join(parent, "nopermstowrite") + if err := os.Mkdir(noPermDir, 0); err != nil { + t.Fatalf("Failed to create the temporary directory %q for testing. Error: %q", noPermDir, err) + } + path := filepath.Join(noPermDir, "foobar") + tarstring := testdata["untar_into_a_directory_we_dont_have_permission_to_write_to"] + if err := common.UnTarString(tarstring, path); err == nil { + t.Fatalf("Should have given an error since we don't have permission to write to the directory %q", path) + } + }) + + t.Run("untar a single file into a directory we don't have permission to write to", func(t *testing.T) { + parent := t.TempDir() + noPermDir := filepath.Join(parent, "nopermstowrite") + if err := os.Mkdir(noPermDir, 0); err != nil { + t.Fatalf("Failed to create the temporary directory %q for testing. Error: %q", noPermDir, err) + } + path := filepath.Join(noPermDir, "foobar") + tarstring := testdata["untar_a_single_file_into_a_directory_we_dont_have_permission_to_write_to"] + if err := common.UnTarString(tarstring, path); err == nil { + t.Fatalf("Should have given an error since we don't have permission to write to the directory %q", path) + } + }) +} diff --git a/internal/common/testdata/datafortestingtar/tobetarred/test1.json b/internal/common/testdata/datafortestingtar/tobetarred/test1.json new file mode 100644 index 000000000..27271cfdf --- /dev/null +++ b/internal/common/testdata/datafortestingtar/tobetarred/test1.json @@ -0,0 +1,5 @@ +{ + "name": "name1", + "foo": 42, + "bar": ["bar"] +} \ No newline at end of file diff --git a/internal/common/testdata/datafortestingtar/tobetarred/test1.yaml b/internal/common/testdata/datafortestingtar/tobetarred/test1.yaml new file mode 100644 index 000000000..7da038c28 --- /dev/null +++ b/internal/common/testdata/datafortestingtar/tobetarred/test1.yaml @@ -0,0 +1,17 @@ +--- +kind: clustermetadata +contextname: name1 +rootuserallowed: false +storageclasses: + - class1 + - class2 +apikindversionmap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/common/testdata/datafortestingtar/tobetarred/test2.yml b/internal/common/testdata/datafortestingtar/tobetarred/test2.yml new file mode 100644 index 000000000..7da038c28 --- /dev/null +++ b/internal/common/testdata/datafortestingtar/tobetarred/test2.yml @@ -0,0 +1,17 @@ +--- +kind: clustermetadata +contextname: name1 +rootuserallowed: false +storageclasses: + - class1 + - class2 +apikindversionmap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/common/testdata/datafortestingtar/untarstring.json b/internal/common/testdata/datafortestingtar/untarstring.json new file mode 100644 index 000000000..a67d96a91 --- /dev/null +++ b/internal/common/testdata/datafortestingtar/untarstring.json @@ -0,0 +1,7 @@ +{ + "untar_a_valid_tarstring": "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", + "untar_an_invalid_base_64_string": "==", + "untar_an_invalid_tarstring": "LgAAAAAAAADAwMDA3NTUAMDAwMDc2NQAwMDAwMDIhcmlrcmlzaG5hbmJhbGFAAc3AAAAAAAtLS0KdmVyc2lvbjogMC4wLjAKZ2l0X2NvbW1pdDogMS4wLjAKZ2l0X3RyZWVfc3RhdGU6IDEuMS4wCmdvX3ZlcnNpb246IDEuMS4xCi4uLg==", + "untar_into_a_directory_we_dont_have_permission_to_write_to": "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", + "untar_a_single_file_into_a_directory_we_dont_have_permission_to_write_to": "LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2NDQAMDAwMDc2NQAwMDAwMDI0ADAwMDAwMDAwMzMxADEzNzIyMjcxNTAyADAxNDY0MwAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMGhhcmlrcmlzaG5hbmJhbGFnb3BhbAAAAAAAAAAAAAAAc3RhZmYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtLS0Ka2luZDogY2x1c3Rlcm1ldGFkYXRhCmNvbnRleHRuYW1lOiBuYW1lMQpyb290dXNlcmFsbG93ZWQ6IGZhbHNlCnN0b3JhZ2VjbGFzc2VzOgogIC0gY2xhc3MxCiAgLSBjbGFzczIKYXBpa2luZHZlcnNpb25tYXA6CiAga2V5MToKICAgIC0gMS4wLjAKICAgIC0gMS4xLjAKICAgIC0gMS4xLjEKICBrZXkyOgogICAgLSAyLjAuMAogICAgLSAyLjIuMAogICAgLSAyLjIuMgouLi4K" +} diff --git a/internal/common/testdata/invalidfiles/test1.json b/internal/common/testdata/invalidfiles/test1.json new file mode 100644 index 000000000..7c08ffd5e --- /dev/null +++ b/internal/common/testdata/invalidfiles/test1.json @@ -0,0 +1,5 @@ +{ + some invalid json "name": "name1", + "foo": 42, + "bar": ["bar"] +} \ No newline at end of file diff --git a/internal/common/testdata/invalidfiles/test1.yaml b/internal/common/testdata/invalidfiles/test1.yaml new file mode 100644 index 000000000..09a253d9d --- /dev/null +++ b/internal/common/testdata/invalidfiles/test1.yaml @@ -0,0 +1,2 @@ +this: is[: not a!{{ real yaml file. +this is invalid yaml used only for testing. \ No newline at end of file diff --git a/internal/common/testdata/validfiles/test1.json b/internal/common/testdata/validfiles/test1.json new file mode 100644 index 000000000..27271cfdf --- /dev/null +++ b/internal/common/testdata/validfiles/test1.json @@ -0,0 +1,5 @@ +{ + "name": "name1", + "foo": 42, + "bar": ["bar"] +} \ No newline at end of file diff --git a/internal/common/testdata/validfiles/test1.yaml b/internal/common/testdata/validfiles/test1.yaml new file mode 100644 index 000000000..63de724aa --- /dev/null +++ b/internal/common/testdata/validfiles/test1.yaml @@ -0,0 +1,16 @@ +--- +kind: ClusterMetadata +contextName: name1 +storageClasses: + - class1 + - class2 +apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/common/testdata/validfiles/test2.yml b/internal/common/testdata/validfiles/test2.yml new file mode 100644 index 000000000..63de724aa --- /dev/null +++ b/internal/common/testdata/validfiles/test2.yml @@ -0,0 +1,16 @@ +--- +kind: ClusterMetadata +contextName: name1 +storageClasses: + - class1 + - class2 +apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/common/testdata/validfiles/versioninfo.json b/internal/common/testdata/validfiles/versioninfo.json new file mode 100644 index 000000000..7217b5367 --- /dev/null +++ b/internal/common/testdata/validfiles/versioninfo.json @@ -0,0 +1,6 @@ +{ + "Version": "0.0.0", + "GitCommit": "1.0.0", + "GitTreeState": "1.1.0", + "GoVersion": "1.1.1" +} \ No newline at end of file diff --git a/internal/common/testdata/validfiles/versioninfo.yaml b/internal/common/testdata/validfiles/versioninfo.yaml new file mode 100644 index 000000000..36c7f4a56 --- /dev/null +++ b/internal/common/testdata/validfiles/versioninfo.yaml @@ -0,0 +1,6 @@ +--- +version: 0.0.0 +gitCommit: 1.0.0 +gitTreeState: 1.1.0 +goVersion: 1.1.1 +... \ No newline at end of file diff --git a/internal/common/utils.go b/internal/common/utils.go new file mode 100644 index 000000000..6da60b69d --- /dev/null +++ b/internal/common/utils.go @@ -0,0 +1,436 @@ +/* +Copyright IBM Corporation 2020 + +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 common + +import ( + "bytes" + "encoding/json" + "hash/crc64" + "io/ioutil" + "math" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/konveyor/move2kube/internal/assets" + "github.com/konveyor/move2kube/types" + log "github.com/sirupsen/logrus" + "github.com/xrash/smetrics" + yaml "gopkg.in/yaml.v3" +) + +//GetFilesByExt returns files by extension +func GetFilesByExt(inputPath string, exts []string) ([]string, error) { + var files []string + if info, err := os.Stat(inputPath); os.IsNotExist(err) { + log.Warnf("Error in walking through files due to : %q", err) + return nil, err + } else if !info.IsDir() { + log.Warnf("The path %q is not a directory.", inputPath) + } + err := filepath.Walk(inputPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %q due to error: %q", path, err) + return nil + } + // Skip directories + if info.IsDir() { + return nil + } + fext := filepath.Ext(path) + for _, ext := range exts { + if fext == ext { + files = append(files, path) + } + } + return nil + }) + if err != nil { + log.Warnf("Error in walking through files due to : %q", err) + return files, err + } + log.Debugf("No of files with %s ext identified : %d", exts, len(files)) + return files, nil +} + +//GetFilesByName returns files by name +func GetFilesByName(inputPath string, names []string) ([]string, error) { + var files []string + if info, err := os.Stat(inputPath); os.IsNotExist(err) { + log.Warnf("Error in walking through files due to : %q", err) + return files, err + } else if !info.IsDir() { + log.Warnf("The path %q is not a directory.", inputPath) + } + err := filepath.Walk(inputPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %q due to error: %q", path, err) + return nil + } + // Skip directories + if info.IsDir() { + return nil + } + fname := filepath.Base(path) + for _, name := range names { + if fname == name { + files = append(files, path) + } + } + return nil + }) + if err != nil { + log.Warnf("Error in walking through files due to : %s", err) + return files, nil + } + log.Debugf("No of files with %s names identified : %d", names, len(files)) + return files, nil +} + +//YamlAttrPresent returns YAML attributes +func YamlAttrPresent(path string, attr string) (bool, interface{}) { + yamlFile, err := ioutil.ReadFile(path) + if err != nil { + log.Warnf("Error in reading yaml file %s: %s. Skipping", path, err) + return false, nil + } + var fileContents map[string]interface{} + err = yaml.Unmarshal(yamlFile, &fileContents) + if err != nil { + log.Warnf("Error in unmarshalling yaml file %s: %s. Skipping", path, err) + return false, nil + } + if value, ok := fileContents[attr]; ok { + log.Debugf("%s file has %s attribute", path, attr) + return true, value + } + return false, nil +} + +// GetImageNameAndTag splits an image full name and returns the image name and tag +func GetImageNameAndTag(image string) (string, string) { + parts := strings.Split(image, "/") + imageAndTag := strings.Split(parts[len(parts)-1], ":") + imageName := imageAndTag[0] + var tag string + if len(imageAndTag) == 1 { + // no tag, assume latest + tag = "latest" + } else { + tag = imageAndTag[1] + } + + return imageName, tag +} + +// WriteYaml writes an yaml to disk +func WriteYaml(outputPath string, data interface{}) error { + var b bytes.Buffer + encoder := yaml.NewEncoder(&b) + encoder.SetIndent(2) + if err := encoder.Encode(data); err != nil { + log.Error("Error while Encoding object") + return err + } + if err := encoder.Close(); err != nil { + log.Error("Error while closing the encoder. Data not written to file", outputPath, "Error:", err) + return err + } + err := ioutil.WriteFile(outputPath, b.Bytes(), DefaultFilePermission) + if err != nil { + log.Errorf("Error writing yaml to file: %s", err) + return err + } + return nil +} + +// ReadYaml reads an yaml into an object +func ReadYaml(file string, data interface{}) error { + yamlFile, err := ioutil.ReadFile(file) + if err != nil { + log.Debugf("Error in reading yaml file %s: %s.", file, err) + return err + } + err = yaml.Unmarshal(yamlFile, data) + if err != nil { + log.Debugf("Error in unmarshalling yaml file %s: %s.", file, err) + return err + } + rv := reflect.ValueOf(data) + if rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Struct { + rv = rv.Elem() + fv := rv.FieldByName("APIVersion") + if fv.IsValid() { + if fv.Kind() == reflect.String { + val := strings.TrimSpace(fv.String()) + if strings.HasPrefix(val, types.SchemeGroupVersion.Group) && !strings.HasSuffix(val, types.SchemeGroupVersion.Version) { + log.Warnf("The application file (%s) was generated using a different version than (%s)", val, types.SchemeGroupVersion.String()) + } + } + } + } + return nil +} + +// WriteJSON writes an json to disk +func WriteJSON(outputPath string, data interface{}) error { + var b bytes.Buffer + encoder := json.NewEncoder(&b) + if err := encoder.Encode(data); err != nil { + log.Error("Error while Encoding object") + return err + } + err := ioutil.WriteFile(outputPath, b.Bytes(), DefaultFilePermission) + if err != nil { + log.Errorf("Error writing json to file: %s", err) + return err + } + return nil +} + +// ReadJSON reads an json into an object +func ReadJSON(file string, data interface{}) error { + jsonFile, err := ioutil.ReadFile(file) + if err != nil { + log.Debugf("Error in reading json file %s: %s.", file, err) + return err + } + err = json.Unmarshal(jsonFile, &data) + if err != nil { + log.Debugf("Error in unmarshalling json file %s: %s.", file, err) + return err + } + return nil +} + +// NormalizeForFilename normalizes a string to only filename valid characters +func NormalizeForFilename(name string) string { + processedString := MakeFileNameCompliant(name) + //TODO: Make it more robust by taking some characters from start and also from end + const maxPrefixLength = 15 + if len(processedString) > maxPrefixLength { + processedString = processedString[0:maxPrefixLength] + } + crc64Table := crc64.MakeTable(0xC96C5795D7870F42) + crc64Int := crc64.Checksum([]byte(name), crc64Table) + return processedString + "_" + strconv.FormatUint(crc64Int, 16) +} + +// NormalizeForServiceName converts the string to be compatible for service name +func NormalizeForServiceName(svcName string) string { + re := regexp.MustCompile("[._]") + newName := strings.ToLower(re.ReplaceAllString(svcName, "-")) + if newName != svcName { + log.Infof("Changing service name to %s from %s", svcName, newName) + } + return newName +} + +// IsStringPresent checks if a value is present in a slice +func IsStringPresent(list []string, value string) bool { + for _, val := range list { + if strings.EqualFold(val, value) { + return true + } + } + return false +} + +// IsIntPresent checks if a value is present in a slice +func IsIntPresent(list []int, value int) bool { + for _, val := range list { + if val == value { + return true + } + } + return false +} + +// MergeStringSlices merges two string slices +func MergeStringSlices(slice1 []string, slice2 []string) []string { + for _, item := range slice2 { + if !IsStringPresent(slice1, item) { + slice1 = append(slice1, item) + } + } + return slice1 +} + +// MergeIntSlices merges two int slices +func MergeIntSlices(slice1 []int, slice2 []int) []int { + for _, item := range slice2 { + if !IsIntPresent(slice1, item) { + slice1 = append(slice1, item) + } + } + return slice1 +} + +// GetStringFromTemplate returns string for a template +func GetStringFromTemplate(tpl string, config interface{}) (string, error) { + var tplbuffer bytes.Buffer + var packageTemplate = template.Must(template.New("").Parse(tpl)) + err := packageTemplate.Execute(&tplbuffer, config) + if err != nil { + log.Warnf("Unable to translate template %q to string using the data %v", tpl, config) + return "", err + } + return tplbuffer.String(), nil +} + +// WriteTemplateToFile writes a templated string to a file +func WriteTemplateToFile(tpl string, config interface{}, writepath string, filemode os.FileMode) error { + var tplbuffer bytes.Buffer + var packageTemplate = template.Must(template.New("").Parse(tpl)) + err := packageTemplate.Execute(&tplbuffer, config) + if err != nil { + log.Warnf("Unable to translate template %q to string using the data %v", tpl, config) + return err + } + err = ioutil.WriteFile(writepath, tplbuffer.Bytes(), filemode) + if err != nil { + log.Warnf("Error writing file at %s : %s", writepath, err) + return err + } + return nil +} + +// GetClosestMatchingString returns the closest matching string for a given search string +func GetClosestMatchingString(options []string, searchstring string) string { + // tokenize all strings + reg := regexp.MustCompile("[^a-zA-Z0-9]+") + searchstring = reg.ReplaceAllString(searchstring, "") + searchstring = strings.ToLower(searchstring) + + leastDistance := math.MaxInt32 + matchString := "" + + // Simply find the option with least distance + for _, option := range options { + // do tokensize the search space string too + tokenizedOption := reg.ReplaceAllString(option, "") + tokenizedOption = strings.ToLower(tokenizedOption) + + currDistance := smetrics.WagnerFischer(tokenizedOption, searchstring, 1, 1, 2) + + if currDistance < leastDistance { + matchString = option + leastDistance = currDistance + } + } + + return matchString +} + +// MergeStringMaps merges two string maps +func MergeStringMaps(map1 map[string]string, map2 map[string]string) map[string]string { + mergedmap := make(map[string]string) + for k, v := range map1 { + mergedmap[k] = v + } + for k, v := range map2 { + mergedmap[k] = v + } + return mergedmap +} + +// MakeFileNameCompliant returns a DNS-1123 standard string +// Motivated by https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +// Also see page 1 "ASSUMPTIONS" heading of https://tools.ietf.org/html/rfc952 +// Also see page 13 of https://tools.ietf.org/html/rfc1123#page-13 +func MakeFileNameCompliant(name string) string { + if len(name) == 0 { + log.Error("The input name is empty.") + return "" + } + baseName := filepath.Base(name) + invalidChars := regexp.MustCompile("[^a-zA-Z0-9-.]+") + processedName := invalidChars.ReplaceAllString(baseName, "-") + if len(processedName) > 63 { + log.Warnf("The processed name %q is longer than 63 characters long.", processedName) + } + first := processedName[0] + last := processedName[len(processedName)-1] + if first == '-' || first == '.' || last == '-' || last == '.' { + log.Warnf("The first and/or last characters of the name %q are not alphanumeric.", processedName) + } + return processedName +} + +// CleanAndFindCommonDirectory finds the common ancestor directory among a list of absolute paths. +// Cleans the paths you give it before finding the directory. +// Also see FindCommonDirectory +func CleanAndFindCommonDirectory(paths []string) string { + cleanedpaths := make([]string, len(paths)) + for i, path := range paths { + cleanedpaths[i] = filepath.Clean(path) + } + return FindCommonDirectory(cleanedpaths) +} + +// FindCommonDirectory finds the common ancestor directory among a list of cleaned absolute paths. +// Will not clean the paths you give it before trying to find the directory. +// Also see CleanAndFindCommonDirectory +func FindCommonDirectory(paths []string) string { + if len(paths) == 0 { + return "" + } + slash := string(filepath.Separator) + commonDir := paths[0] + for commonDir != slash { + found := true + for _, path := range paths { + if !strings.HasPrefix(path+slash, commonDir+slash) { + found = false + break + } + } + if found { + break + } + commonDir = filepath.Dir(commonDir) + } + return commonDir +} + +// CreateAssetsData creates an assets directory and dumps the assets data into it +func CreateAssetsData() (assetsPath string, tempPath string, finalerr error) { + assetsPath = AssetsPath + tempPath = TempPath + + if newTempPath, err := ioutil.TempDir("", TempDirPrefix); err != nil { + log.Errorf("Unable to create temp dir. Defaulting to local path.") + } else { + tempPath = newTempPath + assetsPath = filepath.Join(newTempPath, AssetsDir) + } + + if err := os.MkdirAll(assetsPath, DefaultDirectoryPermission); err != nil { + log.Errorf("Unable to create the assets directory at path %q Error: %q", assetsPath, err) + return "", "", err + } + if err := UnTarString(assets.Tar, assetsPath); err != nil { + log.Errorf("Unable to untar the assets into the assets directory at path %q Error: %q", assetsPath, err) + return "", "", err + } + + return assetsPath, tempPath, nil +} diff --git a/internal/common/utils_test.go b/internal/common/utils_test.go new file mode 100644 index 000000000..2bd9e4fdb --- /dev/null +++ b/internal/common/utils_test.go @@ -0,0 +1,827 @@ +/* +Copyright IBM Corporation 2020 + +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 common_test + +import ( + "fmt" + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/types/info" + log "github.com/sirupsen/logrus" +) + +func TestGetFilesByExt(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get files by extension when the path doesn't exist", func(t *testing.T) { + path1 := "foobar" + if _, err := common.GetFilesByExt(path1, []string{".yaml", ".yml"}); err == nil { + t.Fatal("Should have given an error since the path is non existent.") + } + }) + + t.Run("get files by extension when the path is a file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + filepaths, err := common.GetFilesByExt(path1, []string{".yaml", ".yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + if len(filepaths) != 1 || filepaths[0] != path1 { + t.Fatal("Failed to get the correct paths. Expected:", path1, "Actual:", filepaths) + } + }) + + t.Run("get files by extension in the normal use case", func(t *testing.T) { + path1 := "testdata/validfiles" + filepaths, err := common.GetFilesByExt(path1, []string{".yaml", ".yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + want := []string{"testdata/validfiles/test1.yaml", "testdata/validfiles/test2.yml", "testdata/validfiles/versioninfo.yaml"} + if !reflect.DeepEqual(filepaths, want) { + t.Fatal("Failed to get the correct paths. Expected:", want, "Actual:", filepaths) + } + }) + + t.Run("get files by extension when the directory is empty", func(t *testing.T) { + path1 := t.TempDir() + filepaths, err := common.GetFilesByExt(path1, []string{".yaml", ".yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + if len(filepaths) != 0 { + t.Fatal("Should not have returned any paths. Paths returned:", filepaths) + } + }) + + // TODO: do we need to handle this edge case? + // t.Run("get files by extension when you don't have permissions for the directory", func(t *testing.T) { + // parentDir := t.TempDir() + // path1 := filepath.Join(parentDir, "app1") + // if err := os.Mkdir(path1, 0); err != nil { + // t.Fatal("Failed to create the temporary directory", path1, "for testing. Error:", err) + // } + // if _, err := common.GetFilesByExt(path1, []string{".yaml", ".yml"}); err == nil { + // t.Fatal("Should have given an error since we don't have permissions to read the directory.") + // } + // }) +} + +func TestGetFilesByName(t *testing.T) { + t.Run("get files by name when the path doesn't exist", func(t *testing.T) { + path1 := "foobar" + if _, err := common.GetFilesByName(path1, []string{"test1.yaml", "test2.yml"}); err == nil { + t.Fatal("Should have given an error since the path is non existent. Error:", err) + } + }) + + t.Run("get files by name when the path is a file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + filepaths, err := common.GetFilesByName(path1, []string{"test1.yaml", "test2.yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + if len(filepaths) != 1 || filepaths[0] != path1 { + t.Fatal("Failed to get the correct paths. Expected:", path1, "Actual:", filepaths) + } + }) + + t.Run("get files by name in the normal use case", func(t *testing.T) { + path1 := "testdata/validfiles" + filepaths, err := common.GetFilesByName(path1, []string{"test1.yaml", "test2.yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + want := []string{"testdata/validfiles/test1.yaml", "testdata/validfiles/test2.yml"} + if !reflect.DeepEqual(filepaths, want) { + t.Fatal("Failed to get the correct paths. Expected:", want, "Actual:", filepaths) + } + }) + + t.Run("get files by name when the directory is empty", func(t *testing.T) { + path1 := t.TempDir() + filepaths, err := common.GetFilesByName(path1, []string{"test1.yaml", "test2.yml"}) + if err != nil { + t.Fatal("Should not have given any error. Error:", err) + } + if len(filepaths) != 0 { + t.Fatal("Should not have returned any paths. Paths returned:", filepaths) + } + }) + + // TODO: do we need to handle this edge case? + // t.Run("get files by name when you don't have permissions for the directory", func(t *testing.T) { + // parentDir := t.TempDir() + // path1 := filepath.Join(parentDir, "app1") + // if err := os.Mkdir(path1, 0); err != nil { + // t.Fatal("Failed to create the temporary directory", path1, "for testing. Error:", err) + // } + // if _, err := common.GetFilesByName(path1, []string{"test1.yaml", "test2.yml"}); err == nil { + // t.Fatal("Should have given an error since we don't have permissions to read the directory.") + // } + // }) +} + +func TestYamlAttrPresent(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get attribute from non existent path", func(t *testing.T) { + path1 := "foobar" + attr1 := "attr1" + if ok, _ := common.YamlAttrPresent(path1, attr1); ok { + t.Fatal("Should not have succeeded. The file", path1, "does not exist.") + } + }) + + t.Run("get attribute from invalid yaml file", func(t *testing.T) { + path1 := "testdata/invalidfiles/test1.yaml" + attr1 := "attr1" + if ok, _ := common.YamlAttrPresent(path1, attr1); ok { + t.Fatal("Should not have succeeded. The file", path1, "is not a valid yaml file.") + } + }) + + t.Run("get non existent attribute from yaml file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + attr1 := "attr1" + if ok, _ := common.YamlAttrPresent(path1, attr1); ok { + t.Fatal("Should not have succeeded. The file", path1, "does not contain the attribute", attr1) + } + }) + + t.Run("get attribute from yaml file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + attr1 := "kind" + want := "ClusterMetadata" + if ok, val := common.YamlAttrPresent(path1, attr1); !ok || val != want { + t.Fatal("Failed to get the attribute", attr1, "from the file", path1, "properly. Expected:", want, "Actual:", val) + } + }) +} + +func TestGetImageNameAndTag(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get imagename and tag", func(t *testing.T) { + wantImageName := "getting-started" + wantTag := "1.2.3-alpha.beta.gamma+hello.123.world" + imageNameAndTag := "konveyor/" + wantImageName + ":" + wantTag + imageName, tag := common.GetImageNameAndTag(imageNameAndTag) + if imageName != wantImageName { + t.Fatal("Tag is incorrect. Expected:", wantImageName, "Actual:", imageName) + } else if tag != wantTag { + t.Fatal("Tag is incorrect. Expected:", wantTag, "Actual:", tag) + } + }) + + t.Run("get imagename and tag when there is no tag", func(t *testing.T) { + wantImageName := "getting-started" + wantTag := "latest" + imageNameAndTag := "konveyor/" + wantImageName + imageName, tag := common.GetImageNameAndTag(imageNameAndTag) + if imageName != wantImageName { + t.Fatal("Tag is incorrect. Expected:", wantImageName, "Actual:", imageName) + } else if tag != wantTag { + t.Fatal("Tag is incorrect. Expected:", wantTag, "Actual:", tag) + } + }) + +} + +type givesYamlError struct{} + +func (_ *givesYamlError) MarshalYAML() (interface{}, error) { + return nil, fmt.Errorf("Can't marshal this type to yaml.") +} + +func TestWriteYaml(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("write some data to an invalid path", func(t *testing.T) { + path1 := "/this/does/not/exist/foobar.yaml" + data1 := "contents1" + if err := common.WriteYaml(path1, data1); err == nil { + t.Fatal("Should not have succeeded since the path", path1, "is invalid.") + } + }) + + t.Run("write some data to a yaml file", func(t *testing.T) { + path1 := t.TempDir() + "foobar.yaml" + data1 := struct { + Foo string + Bar int + }{"contents1", 42} + if err := common.WriteYaml(path1, data1); err != nil { + t.Fatal("Failed to write the data", data1, "to the file path", path1, ". Error:", err) + } + want := "foo: contents1\nbar: 42\n" + if yamldata, err := ioutil.ReadFile(path1); err != nil { + t.Fatal("Failed to read the file we just wrote. Error:", err) + } else if yamldatastr := string(yamldata); yamldatastr != want { + t.Fatal("Failed to encode the data to yaml properly. Expected:", want, "Actual:", yamldatastr) + } + }) + + t.Run("write some data that cannot be encoded to yaml to file", func(t *testing.T) { + path1 := t.TempDir() + "foobar.yaml" + data1 := &givesYamlError{} + if err := common.WriteYaml(path1, data1); err == nil { + t.Fatal("Should not have succeeded since the data", data1, "cannot be marshalled to yaml.") + } + }) +} + +func TestReadYaml(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("read some data from non existent path", func(t *testing.T) { + path1 := "foobar" + data1 := struct { + Name string + Tag string + }{"foo", "bar"} + if err := common.ReadYaml(path1, &data1); err == nil { + t.Fatal("Should not have succeeded. The file", path1, "does not exist.") + } + }) + + t.Run("read some data from invalid yaml file", func(t *testing.T) { + path1 := "testdata/invalidfiles/test1.yaml" + data1 := struct { + Name string + Tag string + }{"foo", "bar"} + if err := common.ReadYaml(path1, &data1); err == nil { + t.Fatal("Should not have succeeded. The file", path1, "is not a valid yaml file.") + } + }) + + t.Run("read some non existent keys from yaml file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + data1 := struct { + Name string + Tag string + }{"foo", "bar"} + want := struct { + Name string + Tag string + }{"foo", "bar"} + if err := common.ReadYaml(path1, &data1); err != nil { + t.Fatal("Failed to read yaml from the file", path1, "Error:", err) + } + if data1 != want { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) + + t.Run("read some data from yaml file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.yaml" + data1 := struct { + Kind string `yaml:"kind"` + ContextName string `yaml:"contextName"` + }{"foo", "bar"} + want := struct { + Kind string `yaml:"kind"` + ContextName string `yaml:"contextName"` + }{"ClusterMetadata", "name1"} + if err := common.ReadYaml(path1, &data1); err != nil { + t.Fatal("Failed to read yaml from the file", path1, "Error:", err) + } + if data1 != want { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) + + t.Run("read version info from yaml file", func(t *testing.T) { + path1 := "testdata/validfiles/versioninfo.yaml" + data1 := info.VersionInfo{Version: "0.0.0", GitCommit: "0.0.0", GitTreeState: "0.0.0", GoVersion: "0.0.0"} + want := info.VersionInfo{Version: "0.0.0", GitCommit: "1.0.0", GitTreeState: "1.1.0", GoVersion: "1.1.1"} + if err := common.ReadYaml(path1, &data1); err != nil { + t.Fatal("Failed to read yaml from the file", path1, "Error:", err) + } + if data1 != want { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) +} + +type givesJSONError struct{} + +func (_ *givesJSONError) MarshalJSON() ([]byte, error) { + return nil, fmt.Errorf("Can't marshal this type to json.") +} + +func TestWriteJSON(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("write some data to an invalid path", func(t *testing.T) { + path1 := "/this/does/not/exist/foobar.json" + data1 := "contents1" + if err := common.WriteJSON(path1, data1); err == nil { + t.Fatal("Should not have succeeded since the path", path1, "is invalid.") + } + }) + + t.Run("write some data to a json file", func(t *testing.T) { + path1 := t.TempDir() + "foobar.json" + data1 := struct { + Foo string + Bar int + }{"contents1", 42} + if err := common.WriteJSON(path1, data1); err != nil { + t.Fatal("Failed to write the data", data1, "to the file path", path1, ". Error:", err) + } + want := "{\"Foo\":\"contents1\",\"Bar\":42}\n" + if yamldata, err := ioutil.ReadFile(path1); err != nil { + t.Fatal("Failed to read the file we just wrote. Error:", err) + } else if yamldatastr := string(yamldata); yamldatastr != want { + //log.Errorf("%q", yamldatastr) + t.Fatal("Failed to encode the data to json properly. Expected:", want, "Actual:", yamldatastr) + } + }) + + t.Run("write some data that cannot be encoded to json to file", func(t *testing.T) { + path1 := t.TempDir() + "foobar.json" + data1 := &givesJSONError{} + if err := common.WriteJSON(path1, data1); err == nil { + t.Fatal("Should not have succeeded since the data", data1, "cannot be marshalled to json.") + } + }) +} + +func TestReadJSON(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("read some data from non existent path", func(t *testing.T) { + path1 := "foobar" + data1 := struct { + Name string + Foo int + Bar []string + }{} + if err := common.ReadJSON(path1, &data1); err == nil { + t.Fatal("Should not have succeeded. The file", path1, "does not exist.") + } + }) + + t.Run("read some data from invalid json file", func(t *testing.T) { + path1 := "testdata/invalidfiles/test1.json" + data1 := struct { + Name string + Foo int + Bar []string + }{} + if err := common.ReadJSON(path1, &data1); err == nil { + t.Fatal("Should not have succeeded. The file", path1, "is not a valid json file.") + } + }) + + t.Run("read some non existent keys from json file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.json" + data1 := struct { + Key1 string + Key2 string + }{"foo", "bar"} + want := struct { + Key1 string + Key2 string + }{"foo", "bar"} + if err := common.ReadJSON(path1, &data1); err != nil { + t.Fatal("Failed to read json from the file", path1, "Error:", err) + } + if data1 != want { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) + + t.Run("read some data from json file", func(t *testing.T) { + path1 := "testdata/validfiles/test1.json" + data1 := struct { + Name string + Foo int + Bar []string + }{} + want := struct { + Name string + Foo int + Bar []string + }{"name1", 42, []string{"bar"}} + if err := common.ReadJSON(path1, &data1); err != nil { + t.Fatal("Failed to read json from the file", path1, "Error:", err) + } + if !reflect.DeepEqual(data1, want) { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) + + t.Run("read version info from json file", func(t *testing.T) { + path1 := "testdata/validfiles/versioninfo.json" + data1 := info.VersionInfo{Version: "0.0.0", GitCommit: "0.0.0", GitTreeState: "0.0.0", GoVersion: "0.0.0"} + want := info.VersionInfo{Version: "0.0.0", GitCommit: "1.0.0", GitTreeState: "1.1.0", GoVersion: "1.1.1"} + if err := common.ReadJSON(path1, &data1); err != nil { + t.Fatal("Failed to read json from the file", path1, "Error:", err) + } + if data1 != want { + t.Fatal("Failed to read the data properly. Expected:", want, "Actual:", data1) + } + }) +} + +func TestNormalizeForFilename(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct{ name, in, out string }{ + {"normalize an invalid filename", "foobar%${/2\n\tinv.json.yaml.", "2-inv.json.yaml_d65d80a1c389718f"}, + {"normalize a valid filename", "foobar", "foobar_534a426c0464b01e"}, + {"normalize a long valid filename", "thisisalongfilenamefoobar", "thisisalongfile_730bb88a395ce114"}, + {"normalize a valid filename with an extension", "foobar.json", "foobar.json_f161da8efa921f1f"}, + {"normalize a valid filepath", "path/to/a/file/foobar.json", "foobar.json_b1760918996ebb3"}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + output := common.NormalizeForFilename(tc.in) + if output != tc.out { + t.Fatalf("Failed to normalize the string %q properly. Expected: %q Actual: %q", tc.in, tc.out, output) + } + }) + } +} + +func TestNormalizeForServiceName(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct{ name, in, out string }{ + // {"normalize an invalid service name", "foobar%${/2\n\tinv.website.registration.", "foobar%${/2\n\tinv-website-registration-"}, // TODO: does this edge case have to be handled? + {"normalize an invalid service name", "foobar.website.registration.", "foobar-website-registration-"}, + {"normalize a valid service name", "foobar", "foobar"}, + {"normalize a long valid service name", "thisisalongservicenamefoobar", "thisisalongservicenamefoobar"}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + output := common.NormalizeForServiceName(tc.in) + if output != tc.out { + t.Fatalf("Failed to normalize the string %q properly. Expected: %q Actual: %q", tc.in, tc.out, output) + } + }) + } +} + +func TestIsStringPresent(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inArr []string + inQuery string + out bool + }{ + {"find a string in the array", []string{"foo", "bar"}, "foo", true}, + {"find a non existent string in the array", []string{"foo", "bar"}, "str1", false}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + found := common.IsStringPresent(tc.inArr, tc.inQuery) + if found != tc.out { + if tc.out { + t.Fatalf("Failed to find the string %q in the array %v", tc.inQuery, tc.inArr) + } else { + t.Fatalf("Should not have found the string %q in the array %v", tc.inQuery, tc.inArr) + } + } + }) + } +} + +func TestIsIntPresent(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inArr []int + inQuery int + out bool + }{ + {"find a int in an empty array", []int{}, 0, false}, + {"find a int in an array", []int{100, 0, 1, -1, -42}, 0, true}, + {"find a non existent int in an array", []int{100, 0, 1, -1, -42}, 200, false}, + {"find a int in an array when there are duplicates", []int{100, 0, -42, -42, 1}, -42, true}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + found := common.IsIntPresent(tc.inArr, tc.inQuery) + if found != tc.out { + if tc.out { + t.Fatalf("Failed to find the int %d in the array %v", tc.inQuery, tc.inArr) + } else { + t.Fatalf("Should not have found the int %d in the array %v", tc.inQuery, tc.inArr) + } + } + }) + } +} + +func TestMergeStringSlices(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inArr1 []string + inArr2 []string + out []string + }{ + {"merge 2 empty arrays", []string{}, []string{}, []string{}}, + {"merge a filled array into an empty array", []string{}, []string{"foo", "bar"}, []string{"foo", "bar"}}, + {"merge an empty array into a filled array", []string{"foo", "bar"}, []string{}, []string{"foo", "bar"}}, + {"merge 2 filled arrays", []string{"foo", "bar"}, []string{"foo", "bar", "item1", "item2"}, []string{"foo", "bar", "item1", "item2"}}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + merged := common.MergeStringSlices(tc.inArr1, tc.inArr2) + if !reflect.DeepEqual(merged, tc.out) { + t.Fatalf("Failed to merge the arrays properly. Array1: %v Array2: %v Expected: %v Actual: %v", tc.inArr1, tc.inArr2, tc.out, merged) + } + }) + } +} + +func TestMergeIntSlices(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inArr1 []int + inArr2 []int + out []int + }{ + {"merge 2 empty arrays", []int{}, []int{}, []int{}}, + {"merge a filled array into an empty array", []int{}, []int{100, 0}, []int{100, 0}}, + {"merge an empty array into a filled array", []int{100, -42, -1, 0}, []int{}, []int{100, -42, -1, 0}}, + {"merge 2 filled arrays", []int{100, -42, -1, 0}, []int{10, -1, -1, 0, 2}, []int{100, -42, -1, 0, 10, 2}}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + merged := common.MergeIntSlices(tc.inArr1, tc.inArr2) + if !reflect.DeepEqual(merged, tc.out) { + t.Fatalf("Failed to merge the arrays properly. Array1: %v Array2: %v Expected: %v Actual: %v", tc.inArr1, tc.inArr2, tc.out, merged) + } + }) + } +} + +func TestGetStringFromTemplate(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inTmpl string + inData interface{} + outStr string + outErr bool + }{ + {"fill an empty template with an empty string", "", "", "", false}, + {"fill an empty template with an empty struct with no fields", "", struct{}{}, "", false}, + {"fill an empty template with an empty struct", "", struct { + Name string + ID int + }{}, "", false}, + {"fill an empty template with a filled struct", "", struct { + Name string + ID int + }{"foobar", 42}, "", false}, + {"fill a template with an empty struct with no fields", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct{}{}, "", true}, + {"fill a template with an empty struct", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct { + Name string + ID int + }{}, "Hello! My name is and my ID is 0", false}, + {"fill a template with a filled struct", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct { + Name string + ID int + }{"foobar", 42}, "Hello! My name is foobar and my ID is 42", false}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + filled, err := common.GetStringFromTemplate(tc.inTmpl, tc.inData) + if !tc.outErr { + if err != nil { + t.Fatalf("Got an error while trying to fill the tempate %q with the data %v properly. Expected: %q Actual: Error: %v", tc.inTmpl, tc.inData, tc.outStr, err) + } + if filled != tc.outStr { + t.Fatalf("Failed to fill the tempate %q with the data %v properly. Expected: %q Actual: %v", tc.inTmpl, tc.inData, tc.outStr, filled) + } + } else { + if err == nil { + t.Fatalf("Should not have succeeded in filling the tempate %q with the data %v. Expected an error. Actual: %v", tc.inTmpl, tc.inData, filled) + } + } + }) + } +} + +func TestWriteTemplateToFile(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inTmpl string + inData interface{} + outStr string + outErr bool + }{ + {"fill an empty template with an empty string", "", "", "", false}, + {"fill an empty template with an empty struct with no fields", "", struct{}{}, "", false}, + {"fill an empty template with an empty struct", "", struct { + Name string + ID int + }{}, "", false}, + {"fill an empty template with a filled struct", "", struct { + Name string + ID int + }{"foobar", 42}, "", false}, + {"fill a template with an empty struct with no fields", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct{}{}, "", true}, + {"fill a template with an empty struct", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct { + Name string + ID int + }{}, "Hello! My name is and my ID is 0", false}, + {"fill a template with a filled struct", "Hello! My name is {{.Name}} and my ID is {{.ID}}", struct { + Name string + ID int + }{"foobar", 42}, "Hello! My name is foobar and my ID is 42", false}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + path := t.TempDir() + "test1.go" + err := common.WriteTemplateToFile(tc.inTmpl, tc.inData, path, os.ModePerm) + if !tc.outErr { + if err != nil { + t.Fatalf("Got an error while trying to fill the tempate %q with the data %v and write it to the path %q. Expected: %q Actual: Error: %v", tc.inTmpl, tc.inData, path, tc.outStr, err) + } + filledBytes, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read the file %q that we just wrote.", path) + } + if filled := string(filledBytes); filled != tc.outStr { + t.Fatalf("Failed to fill the tempate %q with the data %v properly. Expected: %q Actual: %v", tc.inTmpl, tc.inData, tc.outStr, filled) + } + } else { + if err == nil { + t.Fatalf("Should not have succeeded in filling the tempate %q with the data %v. Expected an error.", tc.inTmpl, tc.inData) + } + } + }) + } + t.Run("try to write filled template when the path doesn't exist", func(t *testing.T) { + inTmpl := "Hello! My name is {{.Name}} and my ID is {{.ID}}" + inData := struct { + Name string + ID int + }{"foobar", 42} + path := "/this/path/does/not/exist/foobar" + err := common.WriteTemplateToFile(inTmpl, inData, path, os.ModePerm) + if err == nil { + t.Fatalf("Should not have succeeded in writing to the path %q since it doesn't exit. Expected an error.", path) + } + }) +} + +func TestGetClosestMatchingString(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + inArr []string + inQuery string + out string + }{ + {"find the closest in an empty array", []string{}, "foo", ""}, + {"find the closest in the array when the string exists", []string{"foo", "bar"}, "foo", "foo"}, + {"find the closest in the array when the string doesn't exist", []string{"foo", "bar"}, "bar2", "bar"}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + foundStr := common.GetClosestMatchingString(tc.inArr, tc.inQuery) + if foundStr != tc.out { + t.Fatalf("Failed to find the closest string to %q in the array %v. Expected: %q Actual: %q", tc.inQuery, tc.inArr, tc.out, foundStr) + } + }) + } +} + +func TestMergeStringMaps(t *testing.T) { + log.SetLevel(log.DebugLevel) + + type mss = map[string]string + tcs := []struct { + name string + inMap1 mss + inMap2 mss + out mss + }{ + {"merge 2 empty maps", mss{}, mss{}, mss{}}, + {"merge a filled map into an empty map", mss{}, mss{"key1": "val1"}, mss{"key1": "val1"}}, + {"merge an empty map into a filled map", mss{"key1": "val1"}, mss{}, mss{"key1": "val1"}}, + {"merge 2 filled maps", mss{"key1": "val1", "key2": "val2"}, mss{"key2": "newval2", "key3": "val3"}, mss{"key1": "val1", "key2": "newval2", "key3": "val3"}}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + merged := common.MergeStringMaps(tc.inMap1, tc.inMap2) + if !reflect.DeepEqual(merged, tc.out) { + t.Fatalf("Failed to merge the maps properly. Map1: %v Map2: %v Expected: %v Actual: %v", tc.inMap1, tc.inMap2, tc.out, merged) + } + }) + } +} + +func TestMakeFileNameCompliant(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + in string + out string + }{ + {"normalize an empty name", "", ""}, + {"normalize an invalid name", "foo\n123.bar%4.inv#22.-", "foo-123.bar-4.inv-22.-"}, + {"normalize an invalid name", "foo/bar/", "bar"}, + {"normalize an invalid name", "path/prefix/foo_bar_baz", "foo-bar-baz"}, + {"normalize a valid name", "foo.bar.baz", "foo.bar.baz"}, + {"normalize a valid long name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789", "01234567890123456789012345678901234567890123456789012345678901234567890123456789"}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + output := common.MakeFileNameCompliant(tc.in) + if output != tc.out { + t.Fatalf("Failed to normalize the string %q to DNS-1123 standard properly. Expected: %q Actual: %q", tc.in, tc.out, output) + } + }) + } +} + +func TestCleanAndFindCommonDirectory(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + in []string + out string + }{ + {"find common directory when list is empty", []string{}, ""}, + {"normal use case", []string{"/foo/bar/baz", "/foo/bar", "/foo"}, "/foo"}, + {"normal use case and common directory is root", []string{"/foo/bar/baz", "/foo/bar", "/app1/service1/module1"}, "/"}, + {"find common directory when list has unclean paths", []string{"/app1/./service1/", "/app1/service1/module2/", "/app1/./service1/../service1/module1"}, "/app1/service1"}, + {"find common directory when list has unclean paths and common directory is root", []string{"/foo/bar///baz", "/foo/bar///.", "/app1/./service1/../service1/module1"}, "/"}, + {"list has identical paths", []string{"/foo/bar/baz", "/foo/bar/baz", "/foo/bar/baz"}, "/foo/bar/baz"}, + {"list contains root", []string{"/", "/.", "/..", "/.app/.bar", "/.app/.bar"}, "/"}, + {"list contains root", []string{"/foo/bar", "/", "/foo/bar/baz"}, "/"}, + {"list has identical but unclean paths", []string{"/foo/bar/baz////", "/foo/bar/baz", "/foo/bar/baz/././../baz/"}, "/foo/bar/baz"}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + output := common.CleanAndFindCommonDirectory(tc.in) + if output != tc.out { + t.Fatal("Expected:", tc.out, "Actual:", output) + } + }) + } +} + +func TestFindCommonDirectory(t *testing.T) { + log.SetLevel(log.DebugLevel) + + tcs := []struct { + name string + in []string + out string + }{ + {"find common directory when list is empty", []string{}, ""}, + {"normal use case", []string{"/foo/bar/baz", "/foo/bar", "/foo"}, "/foo"}, + {"normal use case and common directory is root", []string{"/foo/bar/baz", "/foo/bar", "/app1/service1/module1"}, "/"}, + {"list has identical paths", []string{"/foo/bar/baz", "/foo/bar/baz", "/foo/bar/baz"}, "/foo/bar/baz"}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + output := common.FindCommonDirectory(tc.in) + if output != tc.out { + t.Fatal("Expected:", tc.out, "Actual:", output) + } + }) + } +} diff --git a/internal/containerizer/cnb/containerruntimeprovider.go b/internal/containerizer/cnb/containerruntimeprovider.go new file mode 100644 index 000000000..12c1c999b --- /dev/null +++ b/internal/containerizer/cnb/containerruntimeprovider.go @@ -0,0 +1,133 @@ +/* +Copyright IBM Corporation 2020 + +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 cnb + +import ( + "errors" + "fmt" + "os/exec" + "path/filepath" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" +) + +type containerRuntimeProvider struct { +} + +var ( + containerRuntime = "" + availableBuilders = []string{} +) + +func (r *containerRuntimeProvider) getAllBuildpacks(builders []string) (map[string][]string, error) { //[Containerization target option value] buildpacks + buildpacks := make(map[string][]string) + containerRuntime, available := r.getContainerRuntime() + if !available { + return buildpacks, errors.New("Container runtime not supported in this instance") + } + log.Debugf("Getting data of all builders %s", builders) + for _, builder := range builders { + inspectcmd := exec.Command(containerRuntime, "inspect", "--format", `{{ index .Config.Labels "`+orderLabel+`"}}`, builder) + log.Debugf("Inspecting image %s", builder) + output, err := inspectcmd.CombinedOutput() + if err != nil { + log.Debugf("Unable to inspect image %s : %s, %s", builder, err, output) + continue + } + buildpacks[builder] = getBuildersFromLabel(string(output)) + } + + return buildpacks, nil +} + +func (r *containerRuntimeProvider) getContainerRuntime() (runtime string, available bool) { + if containerRuntime == "" { + detectcmd := exec.Command("docker", "run", "--rm", "hello-world") + output, err := detectcmd.CombinedOutput() + if err != nil { + log.Debugf("Docker not supported : %s : %s", err, output) + detectcmd := exec.Command("podman", "run", "--rm", "hello-world") + output, err = detectcmd.CombinedOutput() + if err != nil { + log.Debugf("Podman not supported : %s : %s", err, output) + containerRuntime = "none" + return containerRuntime, false + } + containerRuntime = "podman" + return containerRuntime, true + } + containerRuntime = "docker" + return containerRuntime, true + } else if containerRuntime == "none" { + return containerRuntime, false + } + return containerRuntime, true +} + +func (r *containerRuntimeProvider) isBuilderAvailable(builder string) bool { + containerRuntime, available := r.getContainerRuntime() + if !available { + return false + } + if common.IsStringPresent(availableBuilders, builder) { + return true + } + // Check if the image exists locally + existcmd := exec.Command(containerRuntime, "images", "-q", builder) + log.Debugf("Checking if the image %s exists locally", builder) + output, err := existcmd.Output() + if err != nil { + log.Warnf("Error while checking if the builder %s exists locally. Error: %q Output: %q", builder, err, output) + return false + } + if len(output) > 0 { + // Found the image in the local machine, no need to pull. + availableBuilders = append(availableBuilders, builder) + return true + } + + pullcmd := exec.Command(containerRuntime, "pull", builder) + log.Debugf("Pulling image %s", builder) + output, err = pullcmd.CombinedOutput() + if err != nil { + log.Warnf("Error while pulling builder %s : %s : %s", builder, err, output) + return false + } + availableBuilders = append(availableBuilders, builder) + return true +} + +func (r *containerRuntimeProvider) isBuilderSupported(path string, builder string) (bool, error) { + if !r.isBuilderAvailable(builder) { + return false, fmt.Errorf("Builder image not available : %s", builder) + } + containerRuntime, _ := r.getContainerRuntime() + p, err := filepath.Abs(path) + if err != nil { + log.Warnf("Unable to resolve to absolute path : %s", err) + } + detectcmd := exec.Command(containerRuntime, "run", "--rm", "-v", p+":/workspace", builder, "/cnb/lifecycle/detector") + log.Debugf("Running detect on image %s", builder) + output, err := detectcmd.CombinedOutput() + if err != nil { + log.Debugf("Detect failed %s : %s : %s", builder, err, output) + return false, nil + } + return true, nil +} diff --git a/internal/containerizer/cnb/containerruntimeprovider_test.go b/internal/containerizer/cnb/containerruntimeprovider_test.go new file mode 100644 index 000000000..597b5c0ed --- /dev/null +++ b/internal/containerizer/cnb/containerruntimeprovider_test.go @@ -0,0 +1,63 @@ +/* +Copyright IBM Corporation 2020 + +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 cnb + +import ( + "reflect" + "testing" + + log "github.com/sirupsen/logrus" +) + +func TestIsBuilderAvailable(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("normal use case", func(t *testing.T) { + provider := containerRuntimeProvider{} + builder := "cloudfoundry/cnb:cflinuxfs3" + + // Test + if !provider.isBuilderAvailable(builder) { + t.Fatalf("Failed to find the builder %q locally and/or pull it.", builder) + } + }) + + t.Run("normal use case where we get result from cache", func(t *testing.T) { + provider := containerRuntimeProvider{} + builder := "cloudfoundry/cnb:cflinuxfs3" + want := []string{builder} + + // Test + if !provider.isBuilderAvailable(builder) { + t.Fatalf("Failed to find the builder %q locally and/or pull it.", builder) + } + if !reflect.DeepEqual(availableBuilders, want) { + t.Fatalf("Failed to add the builder %q to the list of available builders. Expected: %v Actual %v", builder, want, availableBuilders) + } + if !provider.isBuilderAvailable(builder) { + t.Fatalf("Failed to find the builder %q locally and/or pull it.", builder) + } + }) + + t.Run("check for a non existent image", func(t *testing.T) { + provider := containerRuntimeProvider{} + builder := "this/doesnotexist:foobar" + if provider.isBuilderAvailable(builder) { + t.Fatalf("Should not have succeeded. The builder image %q does not exist", builder) + } + }) +} diff --git a/internal/containerizer/cnb/packprovider.go b/internal/containerizer/cnb/packprovider.go new file mode 100644 index 000000000..81edc4572 --- /dev/null +++ b/internal/containerizer/cnb/packprovider.go @@ -0,0 +1,145 @@ +/* +Copyright IBM Corporation 2020 + +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 cnb + +import ( + "bufio" + "errors" + "os" + "os/exec" + "regexp" + "strings" + "sync" + + "github.com/konveyor/move2kube/types" + log "github.com/sirupsen/logrus" +) + +const ( + dockersock = "/var/run/docker.sock" +) + +type packProvider struct { +} + +func (p *packProvider) isAvailable() bool { + _, err := exec.LookPath("pack") + if err != nil { + log.Debugf("Unable to find pack : %s", err) + return false + } + _, err = os.Stat(dockersock) + if os.IsNotExist(err) { + log.Debugf("Unable to find pack docker socket, ignoring CNB based containerization approach : %s", err) + return false + } + return true +} + +func (p *packProvider) isBuilderSupported(path string, builder string) (bool, error) { + if !p.isAvailable() { + return false, errors.New("Pack not supported in this instance") + } + cmd := exec.Command("pack", "build", types.AppNameShort+"testcflinuxf2selector:1", "-B", builder, "-p", path) + + var wg sync.WaitGroup + + stdout, err := cmd.StdoutPipe() + if err != nil { + log.Errorf("RunCommand: cmd.StdoutPipe(): %v", err) + return false, err + } + stderr, err := cmd.StderrPipe() + if err != nil { + log.Errorf("RunCommand: cmd.StderrPipe(): %v", err) + return false, err + } + + if err := cmd.Start(); err != nil { + log.Errorf("RunCommand: cmd.Start(): %v", err) + return false, err + } + + outch := make(chan string, 10) + + scannerStdout := bufio.NewScanner(stdout) + scannerStdout.Split(bufio.ScanLines) + wg.Add(1) + go func() { + for scannerStdout.Scan() { + text := scannerStdout.Text() + if strings.TrimSpace(text) != "" { + outch <- text + } + } + wg.Done() + }() + scannerStderr := bufio.NewScanner(stderr) + scannerStderr.Split(bufio.ScanLines) + wg.Add(1) + go func() { + for scannerStderr.Scan() { + text := scannerStderr.Text() + if strings.TrimSpace(text) != "" { + outch <- text + } + } + wg.Done() + }() + + go func() { + wg.Wait() + close(outch) + }() + + for t := range outch { + log.Debug(t) + if strings.Contains(t, "===> ANALYZING") { + log.Debugf("Found compatible cnb for %s", path) + _ = cmd.Process.Kill() + return true, nil + } + if strings.Contains(t, "No buildpack groups passed detection.") { + log.Debugf("No compatible cnb for %s", path) + _ = cmd.Process.Kill() + return false, nil + } + } + return false, errors.New("Error while using pack") +} + +func (p *packProvider) getAllBuildpacks(builders []string) (map[string][]string, error) { //[Containerization target option value] buildpacks + buildpacks := make(map[string][]string) + log.Debugf("Getting data of all builders %s", builders) + for _, builder := range builders { + cmd := exec.Command("pack", "inspect-builder", string(builder)) + var buildpackregex = regexp.MustCompile(`(?s)Group\s#\d+:[\r\n\s]+[^\s]+`) + outputStr, err := cmd.Output() + log.Debugf("Builder %s data :%s", builder, outputStr) + if err != nil { + log.Warnf("Error while getting supported buildpacks for builder %s : %s", builder, err) + continue + } + buildpackmatches := buildpackregex.FindAllString(string(outputStr), -1) + log.Debugf("Builder %s data :%s", builder, buildpackmatches) + for _, buildpackmatch := range buildpackmatches { + buildpackfields := strings.Fields(buildpackmatch) + buildpacks[builder] = append(buildpacks[builder], buildpackfields[len(buildpackfields)-1]) + } + } + return buildpacks, nil +} diff --git a/internal/containerizer/cnb/provider.go b/internal/containerizer/cnb/provider.go new file mode 100644 index 000000000..60ddb4296 --- /dev/null +++ b/internal/containerizer/cnb/provider.go @@ -0,0 +1,109 @@ +/* +Copyright IBM Corporation 2020 + +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 cnb + +import ( + "encoding/json" + + log "github.com/sirupsen/logrus" +) + +const ( + orderLabel string = "io.buildpacks.buildpack.order" +) + +var cnbwarnnotsupported = false +var cnbwarnlongwait = true +var cnbproviders = []provider{&containerRuntimeProvider{}, &packProvider{}, &runcProvider{}} + +type order []orderEntry + +type orderEntry struct { + Group []buildpackRef `toml:"group" json:"group"` +} + +type buildpackRef struct { + buildpackInfo + Optional bool `toml:"optional,omitempty" json:"optional,omitempty"` +} + +type buildpackInfo struct { + ID string `toml:"id" json:"id,omitempty"` + Version string `toml:"version" json:"version,omitempty"` + Homepage string `toml:"homepage,omitempty" json:"homepage,omitempty"` +} + +type provider interface { + isBuilderSupported(path string, builder string) (bool, error) + getAllBuildpacks(builders []string) (map[string][]string, error) +} + +// GetAllBuildpacks returns all buildpacks supported +func GetAllBuildpacks(builders []string) (buildpacks map[string][]string) { + for _, cp := range cnbproviders { + buildpacks, err := cp.getAllBuildpacks(builders) + if err == nil && len(buildpacks) > 0 { + return buildpacks + } + } + logCNBNotSupported() + return buildpacks +} + +// IsBuilderSupported returns if a builder supports a path +func IsBuilderSupported(path string, builder string) (valid bool) { + logCNBLongWait() + for _, cp := range cnbproviders { + valid, err := cp.isBuilderSupported(path, builder) + if err == nil { + return valid // if one of the builders reports no support the other builders will as well. + } + } + logCNBNotSupported() + return valid +} + +func logCNBLongWait() { + if !cnbwarnlongwait { + log.Warn("This could take a few minutes to complete.") + cnbwarnlongwait = true + } +} + +func logCNBNotSupported() { + if !cnbwarnnotsupported { + log.Warn("No CNB containerizer method accessible") + cnbwarnnotsupported = true + } +} + +func getBuildersFromLabel(label string) (buildpacks []string) { + buildpacks = []string{} + ogs := order{} + err := json.Unmarshal([]byte(label), &ogs) + if err != nil { + log.Warnf("Unable to read order : %s", err) + return + } + log.Debugf("Builder data :%s", label) + for _, og := range ogs { + for _, buildpackref := range og.Group { + buildpacks = append(buildpacks, buildpackref.ID) + } + } + return +} diff --git a/internal/containerizer/cnb/runcprovider.go b/internal/containerizer/cnb/runcprovider.go new file mode 100644 index 000000000..fd01b7a94 --- /dev/null +++ b/internal/containerizer/cnb/runcprovider.go @@ -0,0 +1,208 @@ +/* +Copyright IBM Corporation 2020 + +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 cnb + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/containers/skopeo/cmd/skopeo/inspect" + ocispec "github.com/opencontainers/runtime-spec/specs-go" + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" +) + +var ( + // CNBContainersPath defines the location of the cnb container cache used by runc + cnbContainersPath string = filepath.Join(common.AssetsPath, "cnb") + runcImagesPath = filepath.Join(cnbContainersPath, "images") + runcBundlesPath = filepath.Join(cnbContainersPath, "bundles") +) + +type runcProvider struct { +} + +func (r *runcProvider) getAllBuildpacks(builders []string) (map[string][]string, error) { //[Containerization target option value] buildpacks + buildpacks := make(map[string][]string) + if !r.isAvailable() { + return buildpacks, errors.New("Runc not supported in this instance") + } + log.Debugf("Getting data of all builders %s", builders) + for _, builder := range builders { + cmd := exec.Command("skopeo", "inspect", "docker://"+string(builder)) + output, err := cmd.CombinedOutput() + log.Debugf("Builder %s data :%s", builder, output) + if err != nil { + log.Warnf("Error while getting supported buildpacks for builder %s : %s", builder, err) + continue + } + sio := inspect.Output{} + err = json.Unmarshal(output, &sio) + if err != nil { + log.Warnf("Unable to seriablize inspect output for builder %s : %s", builder, err) + continue + } + o, found := sio.Labels[orderLabel] + if !found { + log.Warnf("%s missing in builder %s : %s", orderLabel, builder, err) + continue + } + buildpacks[builder] = getBuildersFromLabel(o) + } + return buildpacks, nil +} + +func (r *runcProvider) isAvailable() bool { + _, err := exec.LookPath("runc") + if err != nil { + log.Debugf("Unable to find runc, ignoring runc based cnb check : %s", err) + return false + } + _, err = exec.LookPath("skopeo") + if err != nil { + log.Debugf("Unable to find skopeo, ignoring runc based cnb check : %s", err) + return false + } + _, err = exec.LookPath("umoci") + if err != nil { + log.Debugf("Unable to find umoci, ignoring runc based cnb check : %s", err) + return false + } + return true +} + +func (r *runcProvider) isBuilderAvailable(builder string) bool { + if !r.isAvailable() { + return false + } + r.init([]string{builder}) + image, _ := common.GetImageNameAndTag(builder) + _, err := os.Stat(filepath.Join(runcBundlesPath, image)) + if os.IsNotExist(err) { + log.Debugf("Unable to find pack builder oci bundle, ignoring builder : %s", err) + return false + } + return true +} + +func (r *runcProvider) isBuilderSupported(path string, builder string) (bool, error) { + if !r.isBuilderAvailable(builder) { + return false, fmt.Errorf("Runc Builder image not available : %s", builder) + } + image, _ := common.GetImageNameAndTag(builder) + ociimagespec := ocispec.Spec{} + configfilepath := filepath.Join(runcBundlesPath, image, "config.json") + err := common.ReadJSON(configfilepath, &ociimagespec) + if err != nil { + log.Errorf("Unable to read config for image %s : %s", builder, err) + return false, err + } + + mount := ocispec.Mount{} + mount.Source, _ = filepath.Abs(path) + mount.Destination = "/workspace" + mount.Type = "bind" + mount.Options = []string{"rbind", "ro"} + found := false + for i, m := range ociimagespec.Mounts { + if m.Destination == mount.Destination { + mounts := ociimagespec.Mounts + mounts[i] = mount + ociimagespec.Mounts = mounts + found = true + } + } + if !found { + ociimagespec.Mounts = append(ociimagespec.Mounts, mount) + } + ociimagespec.Process.Args = []string{"/cnb/lifecycle/detector"} + ociimagespec.Process.Terminal = false + err = common.WriteJSON(configfilepath, ociimagespec) + if err != nil { + log.Errorf("Unable to write config json %s : %s", configfilepath, err) + } + + //TODO: Check if two instances of runc can be spawned by two processes with same container name without errors + cmd := exec.Command("runc", "run", "cnbbuilder") + cmd.Dir = filepath.Join(runcBundlesPath, image) + output, err := cmd.CombinedOutput() + if err != nil { + log.Debugf("Error while executing runc %+v at %s : %s, %s, %s", cmd, cmd.Dir, path, output, err) + return false, err + } + + if strings.Contains(string(output), "ERROR: No buildpack groups passed detection.") { + log.Debugf("No compatible cnb for %s", path) + return false, nil + } + return true, nil +} + +func (r *runcProvider) init(builders []string) { + if !r.isAvailable() { + return + } + + err := os.MkdirAll(runcImagesPath, common.DefaultDirectoryPermission) + if err != nil { + log.Debugf("Unable to create cnb directory ignoring runc based cnb check : %s", err) + return + } + + err = os.MkdirAll(runcBundlesPath, common.DefaultDirectoryPermission) + if err != nil { + log.Debugf("Unable to create cnb directory ignoring runc based cnb check : %s", err) + return + } + + for _, builder := range builders { + image, tag := common.GetImageNameAndTag(builder) + if _, err := os.Stat(filepath.Join(runcImagesPath, image)); !os.IsNotExist(err) { + continue + } + skopeocmd := exec.Command("skopeo", "copy", "docker://"+builder, "oci:"+image+":"+tag) + skopeocmd.Dir = runcImagesPath + log.Debugf("Pulling %s", builder) + output, err := skopeocmd.CombinedOutput() + if err != nil { + log.Debugf("Unable to copy image %s : %s, %s", image, err, output) + continue + } else { + log.Debugf("Image pull done : %s", output) + } + fullbundlepath, err := filepath.Abs(filepath.Join(runcBundlesPath, image)) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", fullbundlepath, err) + } + umocicmd := exec.Command("umoci", "unpack", "--image", image+":"+tag, fullbundlepath) + umocicmd.Dir = runcImagesPath + log.Debugf("Creating OCI image %s", builder) + output, err = umocicmd.CombinedOutput() + if err != nil { + log.Debugf("Unable to copy image %s : %s, %s", image, err, output) + continue + } else { + log.Debugf("Image extract done : %s", output) + } + } +} diff --git a/internal/containerizer/cnbcontainerizer.go b/internal/containerizer/cnbcontainerizer.go new file mode 100644 index 000000000..c4dd876f7 --- /dev/null +++ b/internal/containerizer/cnbcontainerizer.go @@ -0,0 +1,97 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "errors" + "path/filepath" + + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer/cnb" + "github.com/konveyor/move2kube/internal/containerizer/scripts" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// CNBContainerizer implements Containerizer interface +type CNBContainerizer struct { + builders []string +} + +// Cache +var cnbcache = make(map[string][]string) + +// Init initializes the containerizer +func (d *CNBContainerizer) Init(path string) { + d.builders = []string{"cloudfoundry/cnb:cflinuxfs3"} + //initRunc(d.builders) + //TODO: Load from CNB Builder name collector +} + +// GetTargetOptions gets all possible target options for a path +func (d *CNBContainerizer) GetTargetOptions(plan plantypes.Plan, path string) []string { + if options, ok := cnbcache[path]; ok { + return options + } + + builders := d.builders + supportedbuilders := []string{} + + for _, builder := range builders { + if cnb.IsBuilderSupported(path, string(builder)) { + supportedbuilders = append(supportedbuilders, builder) + } + } + cnbcache[path] = supportedbuilders + return supportedbuilders +} + +// GetContainerBuildStrategy returns the containerization build strategy for the containerizer +func (d *CNBContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.CNBContainerBuildTypeValue +} + +// GetContainer returns the container for the service +func (d *CNBContainerizer) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + // TODO: Fix exposed ports too + container := irtypes.NewContainer(service.Image, true) + if service.ContainerBuildType == d.GetContainerBuildStrategy() && len(service.ContainerizationTargetOptions) > 0 { + builder := service.ContainerizationTargetOptions[0] + cnbbuilderstring, err := common.GetStringFromTemplate(scripts.CNBBuilder_sh, struct { + ImageName string + Builder string + }{ + ImageName: service.Image, + Builder: builder, + }) + if err != nil { + log.Warnf("Unable to translate template to string : %s", scripts.CNBBuilder_sh) + } else { + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], service.ServiceName+"cnbbuilder.sh"), cnbbuilderstring) + } + container.ExposedPorts = []int{8080} + return container, nil + } + return container, errors.New("Unsupported service type for Containerization or insufficient information in service") +} + +// GetAllBuildpacks returns all supported buildpacks +func (d *CNBContainerizer) GetAllBuildpacks() map[string][]string { + return cnb.GetAllBuildpacks(d.builders) +} diff --git a/internal/containerizer/containerizer.go b/internal/containerizer/containerizer.go new file mode 100644 index 000000000..49b75df5f --- /dev/null +++ b/internal/containerizer/containerizer.go @@ -0,0 +1,86 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "fmt" + + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + log "github.com/sirupsen/logrus" +) + +//go:generate go run github.com/konveyor/move2kube/internal/common/generator scripts + +// Containerizer interface defines interface for containerizing applications +type Containerizer interface { + Init(path string) + GetTargetOptions(plan plantypes.Plan, path string) []string + GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) + GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue +} + +// Containerizers keep track of all initialized containerizers +type Containerizers struct { + containerizers []Containerizer +} + +// ContainerizationOption defines the containerization option for a path +type ContainerizationOption struct { + ContainerizationType plantypes.ContainerBuildTypeValue + TargetOptions []string +} + +// InitContainerizers initializes the containerizers +func (c *Containerizers) InitContainerizers(path string) { + c.containerizers = []Containerizer{new(DockerfileContainerizer), new(S2IContainerizer), new(CNBContainerizer), new(ReuseContainerizer)} + for _, containerizer := range c.containerizers { + containerizer.Init(path) + containerizer.Init(common.AssetsPath) + } +} + +// GetContainerizationOptions returns ContainerizerOptions for given sourcepath +func (c *Containerizers) GetContainerizationOptions(plan plantypes.Plan, sourcepath string) []ContainerizationOption { + cops := make([]ContainerizationOption, 0) + for _, containerizer := range c.containerizers { + if targetoptions := containerizer.GetTargetOptions(plan, sourcepath); len(targetoptions) != 0 { + cops = append(cops, ContainerizationOption{ + ContainerizationType: containerizer.GetContainerBuildStrategy(), + TargetOptions: targetoptions, + }) + } + } + return cops +} + +// GetContainer get the container for a service +func (c *Containerizers) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + for _, containerizer := range c.containerizers { + if containerizer.GetContainerBuildStrategy() == service.ContainerBuildType { + log.Debugf("Containerizing %s using %s", service.ServiceName, service.ContainerBuildType) + container, err := containerizer.GetContainer(plan, service) + if err != nil { + log.Errorf("Error during containerization : %s", err) + return irtypes.Container{}, err + } + return container, nil + } + } + return irtypes.Container{}, fmt.Errorf("Containerization strategy invalid for service : %s", service.ServiceName) +} diff --git a/internal/containerizer/dockerfilecontainerizer.go b/internal/containerizer/dockerfilecontainerizer.go new file mode 100644 index 000000000..3b53065b3 --- /dev/null +++ b/internal/containerizer/dockerfilecontainerizer.go @@ -0,0 +1,179 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer/scripts" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +const ( + dockerfileDetectscript string = types.AppNameShort + "dfdetect.sh" +) + +// DockerfileContainerizer implements Containerizer interface +type DockerfileContainerizer struct { + dfcontainerizers []string //Paths to directories containing containerizers +} + +// Init initializes docker file containerizer +func (d *DockerfileContainerizer) Init(path string) { + var files, err = common.GetFilesByName(path, []string{dockerfileDetectscript}) + if err != nil { + log.Warnf("Unable to fetch files to recognize docker detect scripts : %s", err) + } + for _, file := range files { + fpath := filepath.Dir(file) + d.dfcontainerizers = append(d.dfcontainerizers, fpath) + } + log.Debugf("Detected dockerfile containerization options : %s ", d.dfcontainerizers) +} + +// GetTargetOptions returns the target options for a path +func (d *DockerfileContainerizer) GetTargetOptions(plan plantypes.Plan, path string) []string { + targetOptions := []string{} + for _, dfcontainerizer := range d.dfcontainerizers { + abspath, err := filepath.Abs(path) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", abspath, err) + } + outputStr, err := d.detect(dfcontainerizer, abspath) + log.Debugf("Detect output of %s : %s", dfcontainerizer, outputStr) + if err != nil { + log.Debugf("%s detector cannot containerize %s : %s", dfcontainerizer, path, err) + continue + } else { + if path != common.AssetsPath { + dfcontainerizer, _ = plan.GetRelativePath(dfcontainerizer) + } + targetOptions = append(targetOptions, dfcontainerizer) + } + } + return targetOptions +} + +func (d *DockerfileContainerizer) detect(scriptpath string, directory string) (string, error) { + cmd := exec.Command("/bin/sh", dockerfileDetectscript, directory) + cmd.Dir = scriptpath + log.Debugf("Executing detect script %s on %s : %s", scriptpath, directory, cmd) + outputbytes, err := cmd.Output() + return string(outputbytes), err +} + +// GetContainerBuildStrategy returns the ContaierBuildStrategy +func (d *DockerfileContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.DockerFileContainerBuildTypeValue +} + +// GetContainer returns the container for a service +func (d *DockerfileContainerizer) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + // TODO: Fix exposed ports too + if service.ContainerBuildType != d.GetContainerBuildStrategy() || len(service.ContainerizationTargetOptions) == 0 { + return irtypes.Container{}, fmt.Errorf("Unsupported service type for Containerization or insufficient information in service") + } + container := irtypes.NewContainer(service.Image, true) + dfdirectory := plan.GetFullPath(service.ContainerizationTargetOptions[0]) + content, err := ioutil.ReadFile(filepath.Join(dfdirectory, "Dockerfile")) + dockerfilename := "Dockerfile." + service.ServiceName + if err != nil { + log.Errorf("Unable to read docker file at %s : %s", dfdirectory, err) + return container, err + } + dockerfilestring := string(content) + abspath, err := filepath.Abs(plan.GetFullPath(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0])) + if err != nil { + log.Errorf("Unable to resolve full path of directory %q Error: %q", abspath, err) + return container, err + } + + outputStr, err := d.detect(dfdirectory, abspath) + if err != nil { + log.Errorf("Detect failed : %s", err) + return container, err + } + if outputStr != "" { + m := map[string]interface{}{} + if err := json.Unmarshal([]byte(outputStr), &m); err != nil { + log.Errorf("Unable to unmarshal the output of the detect script at path %q. Output: %q Error: %q", dfdirectory, outputStr, err) + return container, err + } + + if value, present := m["Port"]; present { + portToExpose := int(value.(float64)) + container.ExposedPorts = append(container.ExposedPorts, portToExpose) + } + + dockerfilestring, err = common.GetStringFromTemplate(string(content), m) + if err != nil { + log.Warnf("Template conversion failed : %s", err) + } + } + //log.Debugf("Creating Dockerfile at %s with %s", filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], service.ServiceName+"Dockerfile"), dockerfilestring) + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], dockerfilename), dockerfilestring) + dockerbuildscript, err := common.GetStringFromTemplate(scripts.Dockerbuild_sh, struct { + Dockerfilename string + ImageName string + Context string + }{ + Dockerfilename: dockerfilename, + ImageName: service.Image, + Context: ".", + }) + if err != nil { + log.Warnf("Unable to translate template to string : %s", scripts.Dockerbuild_sh) + } else { + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], service.ServiceName+"dockerbuild.sh"), dockerbuildscript) + } + err = filepath.Walk(dfdirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %s due to error: %s", path, err) + return nil + } + // Skip directories + if info.IsDir() { + return nil + } + filename := filepath.Base(path) + if filename == "Dockerfile" || filename == dockerfileDetectscript { + return nil + } + content, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(err) + } + //TODO: Should we allow subdirectories? + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], filename), string(content)) + return nil + }) + if err != nil { + log.Warnf("Error in walking through files due to : %s", err) + } + + return container, nil +} diff --git a/internal/containerizer/dockerfilecontainerizer_test.go b/internal/containerizer/dockerfilecontainerizer_test.go new file mode 100644 index 000000000..77d2f0559 --- /dev/null +++ b/internal/containerizer/dockerfilecontainerizer_test.go @@ -0,0 +1,100 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer_test + +import ( + "os" + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +func TestDockerFileGetContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container for the sample nodejs app using dockerfile", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/dockerfilecontainerizer/getcontainer/normal/" + want := irtypes.Container{} + mustreadyaml(t, join(testdatapath, "container.yaml"), &want) + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + dockerfilecontainerizer := new(containerizer.DockerfileContainerizer) + + // Test + cont, err := dockerfilecontainerizer.GetContainer(plan, service) + if err != nil { + t.Fatal("Failed to get the container. Error:", err) + } + if !reflect.DeepEqual(cont, want) { + t.Fatal("Failed to create the container properly. Expected:", want, "Actual:", cont) + } + }) + + t.Run("get container for the dockerfile sample when the service is wrong", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/dockerfilecontainerizer/getcontainer/incorrectservice/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + dockerfilecontainerizer := new(containerizer.DockerfileContainerizer) + + // Test + if _, err := dockerfilecontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the incorrect target options.") + } + }) + + t.Run("get container for the dockerfile sample when the container build type is wrong", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + dockerfilecontainerizer := new(containerizer.DockerfileContainerizer) + + // Test + if _, err := dockerfilecontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the wrong builder type.") + } + }) +} diff --git a/internal/containerizer/manualcontainerizer.go b/internal/containerizer/manualcontainerizer.go new file mode 100644 index 000000000..cbb86f3ad --- /dev/null +++ b/internal/containerizer/manualcontainerizer.go @@ -0,0 +1,52 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "fmt" + + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// ManualContainerizer implements Containerizer interface +type ManualContainerizer struct { +} + +// Init initializes the containerizer +func (d *ManualContainerizer) Init(path string) { +} + +// GetTargetOptions returns empty for Manual +func (d ManualContainerizer) GetTargetOptions(plan plantypes.Plan, path string) []string { + return []string{} +} + +// GetContainerBuildStrategy returns the containerbuildstrategy +func (d ManualContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.ManualContainerBuildTypeValue +} + +// GetContainer returns the container for a service +func (d ManualContainerizer) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + // TODO: Fix exposed ports too + if service.ContainerBuildType == d.GetContainerBuildStrategy() { + container := irtypes.NewContainer(service.Image, true) + return container, nil + } + return irtypes.Container{}, fmt.Errorf("Unsupported service type for Containerization or insufficient information in service") +} diff --git a/internal/containerizer/manualcontainerizer_test.go b/internal/containerizer/manualcontainerizer_test.go new file mode 100644 index 000000000..baaac834c --- /dev/null +++ b/internal/containerizer/manualcontainerizer_test.go @@ -0,0 +1,73 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer_test + +import ( + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +func TestManualGetContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container for the sample nodejs app", func(t *testing.T) { + // Setup + + // Test data + testdatapath := "testdata/manualcontainerizer/getcontainer/normal/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + want := irtypes.NewContainer(service.Image, true) + + manualcontainerizer := new(containerizer.ManualContainerizer) + + // Test + cont, err := manualcontainerizer.GetContainer(plan, service) + if err != nil { + t.Fatal("Failed to get the container. Error:", err) + } + if !reflect.DeepEqual(cont, want) { + t.Fatal("Failed to create the container properly. Expected:", want, "Actual:", cont) + } + }) + + t.Run("get container for the nodejs app when the service is wrong", func(t *testing.T) { + // Setup + + // Test data + testdatapath := "testdata/manualcontainerizer/getcontainer/incorrectservice/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + manualcontainerizer := new(containerizer.ManualContainerizer) + + // Test + if _, err := manualcontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the incorrect target options.") + } + }) +} diff --git a/internal/containerizer/reusecontainerizer.go b/internal/containerizer/reusecontainerizer.go new file mode 100644 index 000000000..023df470b --- /dev/null +++ b/internal/containerizer/reusecontainerizer.go @@ -0,0 +1,52 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "fmt" + + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// ReuseContainerizer implements Containerizer interface +type ReuseContainerizer struct { +} + +// Init initializes the containerizer +func (d *ReuseContainerizer) Init(path string) { +} + +// GetTargetOptions does nothing for reuse containerizer +func (d ReuseContainerizer) GetTargetOptions(plan plantypes.Plan, path string) []string { + return []string{} +} + +// GetContainerBuildStrategy returns the containerbuildstrategy that is supported +func (d ReuseContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.ReuseContainerBuildTypeValue +} + +// GetContainer returns the container for a service +func (d ReuseContainerizer) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + // TODO: Fix exposed ports too + if service.ContainerBuildType == d.GetContainerBuildStrategy() { + container := irtypes.NewContainer(service.Image, false) + return container, nil + } + return irtypes.Container{}, fmt.Errorf("Unsupported service type for Containerization or insufficient information in service") +} diff --git a/internal/containerizer/reusecontainerizer_test.go b/internal/containerizer/reusecontainerizer_test.go new file mode 100644 index 000000000..8c31a1bb8 --- /dev/null +++ b/internal/containerizer/reusecontainerizer_test.go @@ -0,0 +1,72 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer_test + +import ( + "reflect" + "testing" + + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + log "github.com/sirupsen/logrus" +) + +func TestReuseGetContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container for the sample nodejs app", func(t *testing.T) { + // Setup + + // Test data + testdatapath := "testdata/reusecontainerizer/getcontainer/normal/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + want := irtypes.NewContainer(service.Image, false) + + reusecontainerizer := new(containerizer.ReuseContainerizer) + + // Test + cont, err := reusecontainerizer.GetContainer(plan, service) + if err != nil { + t.Fatal("Failed to get the container. Error:", err) + } + if !reflect.DeepEqual(cont, want) { + t.Fatal("Failed to create the container properly. Expected:", want, "Actual:", cont) + } + }) + + t.Run("get container for the nodejs app when the service is wrong", func(t *testing.T) { + // Setup + + // Test data + testdatapath := "testdata/reusecontainerizer/getcontainer/incorrectservice/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + reusecontainerizer := new(containerizer.ReuseContainerizer) + + // Test + if _, err := reusecontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the incorrect target options.") + } + }) +} diff --git a/internal/containerizer/reusedockerfilecontainerizer.go b/internal/containerizer/reusedockerfilecontainerizer.go new file mode 100644 index 000000000..b3507bbd8 --- /dev/null +++ b/internal/containerizer/reusedockerfilecontainerizer.go @@ -0,0 +1,70 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer/scripts" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// ReuseDockerfileContainerizer uses its own containerization interface +type ReuseDockerfileContainerizer struct { +} + +// GetContainerBuildStrategy returns the containerization build strategy +func (d *ReuseDockerfileContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.ReuseDockerFileContainerBuildTypeValue +} + +// GetContainer returns the container for the service +func (d *ReuseDockerfileContainerizer) GetContainer(path string, serviceName string, imageName string, dockerfile string, context string) (irtypes.Container, error) { + container := irtypes.NewContainer(imageName, true) + _, err := os.Stat(filepath.Join(path, dockerfile)) + if os.IsNotExist(err) { + log.Errorf("Unable to find docker file %s : %s", dockerfile, err) + log.Errorf("Will assume the dockerfile will be copied and will proceed.") + } + if context == "" { + context = "." + } + if dockerfile == "" { + dockerfile = "Dockerfile" + } + dockerbuildscript, err := common.GetStringFromTemplate(scripts.Dockerbuild_sh, struct { + Dockerfilename string + ImageName string + Context string + }{ + Dockerfilename: dockerfile, + ImageName: imageName, + Context: context, + }) + if err != nil { + log.Warnf("Unable to translate template to string : %s", scripts.Dockerbuild_sh) + } else { + container.AddFile(filepath.Join(path, serviceName+"dockerbuild.sh"), dockerbuildscript) + } + + return container, nil +} diff --git a/internal/containerizer/s2icontainerizer.go b/internal/containerizer/s2icontainerizer.go new file mode 100644 index 000000000..893736150 --- /dev/null +++ b/internal/containerizer/s2icontainerizer.go @@ -0,0 +1,176 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer/scripts" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +const ( + s2iDetectscript string = types.AppNameShort + "s2idetect.sh" +) + +// S2IContainerizer implements Containerizer interface +type S2IContainerizer struct { + s2icontainerizers []string //Paths to directories containing containerizers +} + +// Init initializes the containerizer +func (d *S2IContainerizer) Init(path string) { + var files, err = common.GetFilesByName(path, []string{s2iDetectscript}) + if err != nil { + log.Warnf("Unable to fetch files to recognize s2i detect files : %s", err) + } + for _, file := range files { + fpath := filepath.Dir(file) + d.s2icontainerizers = append(d.s2icontainerizers, fpath) + } + log.Debugf("Detected S2I containerization options : %s ", d.s2icontainerizers) +} + +// GetTargetOptions returns the target options for a path +func (d *S2IContainerizer) GetTargetOptions(plan plantypes.Plan, path string) []string { + abspath, err := filepath.Abs(path) + if err != nil { + log.Errorf("Unable to resolve path %q Error: %q", abspath, err) + return nil + } + targetOptions := []string{} + for _, s2icontainerizer := range d.s2icontainerizers { + outputStr, err := d.detect(s2icontainerizer, abspath) + log.Debugf("Detect output of %q : %q", s2icontainerizer, outputStr) + if err != nil { + log.Debugf("%q detector cannot containerize %q Error: %q", s2icontainerizer, path, err) + continue + } + if path != common.AssetsPath { + s2icontainerizer, _ = plan.GetRelativePath(s2icontainerizer) + } + targetOptions = append(targetOptions, s2icontainerizer) + } + return targetOptions +} + +func (d *S2IContainerizer) detect(scriptpath string, directory string) (string, error) { + cmd := exec.Command("/bin/sh", s2iDetectscript, directory) + cmd.Dir = scriptpath + log.Debugf("Executing detect script %q on %q : %q", scriptpath, directory, cmd) + outputbytes, err := cmd.Output() + return string(outputbytes), err +} + +// GetContainerBuildStrategy returns the containerization build strategy +func (d *S2IContainerizer) GetContainerBuildStrategy() plantypes.ContainerBuildTypeValue { + return plantypes.S2IContainerBuildTypeValue +} + +// GetContainer returns the container for a service +func (d *S2IContainerizer) GetContainer(plan plantypes.Plan, service plantypes.Service) (irtypes.Container, error) { + if service.ContainerBuildType != d.GetContainerBuildStrategy() || len(service.ContainerizationTargetOptions) == 0 { + return irtypes.Container{}, fmt.Errorf("Unsupported service type for Containerization or insufficient information in service") + } + + container := irtypes.NewContainer(service.Image, true) + dfdirectory := plan.GetFullPath(service.ContainerizationTargetOptions[0]) + abspath, err := filepath.Abs(plan.GetFullPath(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0])) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", abspath, err) + return container, err + } + outputStr, err := d.detect(dfdirectory, abspath) + if err != nil { + log.Errorf("Detect failed for S2I (%s) : %s (%s)", dfdirectory, err, outputStr) + return container, err + } + outputStr = strings.TrimSpace(outputStr) + + m := map[string]interface{}{} + if err := json.Unmarshal([]byte(outputStr), &m); err != nil { + log.Errorf("Unable to unmarshal the output of the detect script at path %q. Output: %q Error: %q", dfdirectory, outputStr, err) + return container, err + } + + if value, ok := m["Port"]; ok { + portToExpose := int(value.(float64)) // json numbers are float64 + container.ExposedPorts = append(container.ExposedPorts, portToExpose) + } + + m["ImageName"] = service.Image + s2ibuildscript, err := common.GetStringFromTemplate(scripts.S2IBuilder_sh, m) + if err != nil { + log.Errorf("Unable to translate the template %q to string. Error: %q", scripts.S2IBuilder_sh, err) + return container, err + } + + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], service.ServiceName+"s2ibuild.sh"), s2ibuildscript) + + var relFilePath string + err = filepath.Walk(dfdirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %s due to error: %s", path, err) + return nil + } + if info.IsDir() { + return nil + } + filename := filepath.Base(path) + if filename == s2iDetectscript { + return nil + } + + relFilePath, err = filepath.Rel(dfdirectory, path) + if err != nil { + log.Errorf("Skipping path %q . Failed to get the relative path. Error: %q", path, err) + return nil + } + + tmpl, err := ioutil.ReadFile(path) + if err != nil { + log.Errorf("Skipping path %q . Failed to read the template. Error: %q", path, err) + return nil + } + + contentStr, err := common.GetStringFromTemplate(string(tmpl), m) + if err != nil { + log.Errorf("Skipping path %q . Unable to translate the template to string. Error %q", path, err) + return nil + } + + //Allowing sub-directories + container.AddFile(filepath.Join(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0], relFilePath), contentStr) + return nil + }) + if err != nil { + log.Warnf("Error in walking through files due to : %s", err) + } + + return container, nil +} diff --git a/internal/containerizer/s2icontainerizer_test.go b/internal/containerizer/s2icontainerizer_test.go new file mode 100644 index 000000000..42aaa4ce7 --- /dev/null +++ b/internal/containerizer/s2icontainerizer_test.go @@ -0,0 +1,120 @@ +/* +Copyright IBM Corporation 2020 + +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 containerizer_test + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + log "github.com/sirupsen/logrus" +) + +// Helper functions +var join = filepath.Join + +func mustreadyaml(t *testing.T, path string, x interface{}) { + if err := common.ReadYaml(path, x); err != nil { + t.Fatalf("Failed to read the testdata at path %q Error: %q", path, err) + } +} + +func setupAssets(t *testing.T) { + assetsPath, tempPath, err := common.CreateAssetsData() + if err != nil { + t.Fatalf("Unable to create the assets directory. Error: %q", err) + } + + common.TempPath = tempPath + common.AssetsPath = assetsPath +} + +// Tests +func TestS2IGetContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container for the sample nodejs app", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/s2icontainerizer/getcontainer/normal/" + want := irtypes.Container{} + mustreadyaml(t, join(testdatapath, "container.yaml"), &want) + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + s2icontainerizer := new(containerizer.S2IContainerizer) + + // Test + cont, err := s2icontainerizer.GetContainer(plan, service) + if err != nil { + t.Fatal("Failed to get the container. Error:", err) + } + if !reflect.DeepEqual(cont, want) { + t.Fatal("Failed to create the container properly. Expected:", want, "Actual:", cont) + } + }) + + t.Run("get container for the nodejs app when the service is wrong", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/s2icontainerizer/getcontainer/incorrectservice/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + s2icontainerizer := new(containerizer.S2IContainerizer) + + // Test + if _, err := s2icontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the incorrect target options.") + } + }) + + t.Run("get container for the nodejs app when the container build type is wrong", func(t *testing.T) { + // Setup + setupAssets(t) + defer os.RemoveAll(common.TempPath) + + // Test data + testdatapath := "testdata/s2icontainerizer/getcontainer/incorrectbuilder/" + plan := plantypes.Plan{} + mustreadyaml(t, join(testdatapath, "plan.yaml"), &plan) + service := plantypes.Service{} + mustreadyaml(t, join(testdatapath, "service.yaml"), &service) + + s2icontainerizer := new(containerizer.S2IContainerizer) + + // Test + if _, err := s2icontainerizer.GetContainer(plan, service); err == nil { + t.Fatal("Should not have succeeded since the service has the wrong builder type.") + } + }) +} diff --git a/internal/containerizer/scripts/CNBBuilder.sh b/internal/containerizer/scripts/CNBBuilder.sh new file mode 100644 index 000000000..3bf24ca34 --- /dev/null +++ b/internal/containerizer/scripts/CNBBuilder.sh @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +pack build {{ .ImageName }} -B {{ .Builder }} \ No newline at end of file diff --git a/internal/containerizer/scripts/Dockerbuild.sh b/internal/containerizer/scripts/Dockerbuild.sh new file mode 100644 index 000000000..11adaf9fa --- /dev/null +++ b/internal/containerizer/scripts/Dockerbuild.sh @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +docker build -f {{ .Dockerfilename }} -t {{ .ImageName }} {{ .Context }} \ No newline at end of file diff --git a/internal/containerizer/scripts/S2IBuilder.sh b/internal/containerizer/scripts/S2IBuilder.sh new file mode 100644 index 000000000..05d4c01cc --- /dev/null +++ b/internal/containerizer/scripts/S2IBuilder.sh @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +s2i build . {{ .Builder }} {{ .ImageName }} \ No newline at end of file diff --git a/internal/containerizer/scripts/constants.go b/internal/containerizer/scripts/constants.go new file mode 100644 index 000000000..08c4211f4 --- /dev/null +++ b/internal/containerizer/scripts/constants.go @@ -0,0 +1,73 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2020-09-14 15:52:45.608874 +0530 IST m=+0.001107857 + +/* +Copyright IBM Corporation 2020 + +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 scripts + +const ( + + CNBBuilder_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +pack build {{ .ImageName }} -B {{ .Builder }}` + + Dockerbuild_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +docker build -f {{ .Dockerfilename }} -t {{ .ImageName }} {{ .Context }}` + + S2IBuilder_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +s2i build . {{ .Builder }} {{ .ImageName }}` + +) \ No newline at end of file diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/plan.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/plan.yaml new file mode 100644 index 000000000..9c071d4ed --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: NewDockerfile + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/service.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/service.yaml new file mode 100644 index 000000000..1589e3e8e --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectbuilder/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: S2I +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/nodejs +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/plan.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/plan.yaml new file mode 100644 index 000000000..9c071d4ed --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: NewDockerfile + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/service.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/service.yaml new file mode 100644 index 000000000..7dd6c1080 --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/incorrectservice/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: NewDockerfile +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/java +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/container.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/container.yaml new file mode 100644 index 000000000..dc26b4394 --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/container.yaml @@ -0,0 +1,45 @@ +imagenames: + - dockerfile:latest +new: true +newfiles: + Dockerfile.dockerfile: |- + # Copyright IBM Corporation 2020 + # + # 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. + + + FROM registry.access.redhat.com/ubi8/nodejs-12 + ADD . . + RUN npm install + EXPOSE 8080 + CMD npm run -d start + dockerfiledockerbuild.sh: |- + # Copyright IBM Corporation 2020 + # + # 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. + + docker build -f Dockerfile.dockerfile -t dockerfile:latest . +exposedports: + - 8080 +userid: -1 +accesseddirs: [] diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/plan.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/plan.yaml new file mode 100644 index 000000000..9c071d4ed --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: NewDockerfile + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/service.yaml b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/service.yaml new file mode 100644 index 000000000..66c0b9247 --- /dev/null +++ b/internal/containerizer/testdata/dockerfilecontainerizer/getcontainer/normal/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: NewDockerfile +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/nodejs +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/plan.yaml b/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/plan.yaml new file mode 100644 index 000000000..3bc0a67a7 --- /dev/null +++ b/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: Manual + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/service.yaml b/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/service.yaml new file mode 100644 index 000000000..7dd6c1080 --- /dev/null +++ b/internal/containerizer/testdata/manualcontainerizer/getcontainer/incorrectservice/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: NewDockerfile +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/java +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/plan.yaml b/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/plan.yaml new file mode 100644 index 000000000..3bc0a67a7 --- /dev/null +++ b/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: Manual + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/service.yaml b/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/service.yaml new file mode 100644 index 000000000..8a95d26a0 --- /dev/null +++ b/internal/containerizer/testdata/manualcontainerizer/getcontainer/normal/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: Manual +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/java +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/plan.yaml b/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/plan.yaml new file mode 100644 index 000000000..fb865701e --- /dev/null +++ b/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: Reuse + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/service.yaml b/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/service.yaml new file mode 100644 index 000000000..7dd6c1080 --- /dev/null +++ b/internal/containerizer/testdata/reusecontainerizer/getcontainer/incorrectservice/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: NewDockerfile +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/java +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/plan.yaml b/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/plan.yaml new file mode 100644 index 000000000..fb865701e --- /dev/null +++ b/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/dockerfile/ + services: + dockerfile: + - serviceName: dockerfile + image: dockerfile:latest + translationType: Any2Kube + containerBuildType: Reuse + sourceType: + - Directory + targetOptions: + - m2kassets/dockerfiles/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/service.yaml b/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/service.yaml new file mode 100644 index 000000000..720287321 --- /dev/null +++ b/internal/containerizer/testdata/reusecontainerizer/getcontainer/normal/service.yaml @@ -0,0 +1,16 @@ +serviceName: dockerfile +image: dockerfile:latest +translationType: Any2Kube +containerBuildType: Reuse +sourceType: + - Directory +targetOptions: + - m2kassets/dockerfiles/java +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/plan.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/plan.yaml new file mode 100644 index 000000000..387f55458 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/nodejs + services: + nodejs: + - serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: S2I + sourceType: + - Directory + targetOptions: + - m2kassets/s2i/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/service.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/service.yaml new file mode 100644 index 000000000..c2aaff5c1 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectbuilder/service.yaml @@ -0,0 +1,16 @@ +serviceName: nodejs +image: nodejs:latest +translationType: Any2Kube +containerBuildType: NewDockerfile +sourceType: + - Directory +targetOptions: + - m2kassets/s2i/nodejs +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/plan.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/plan.yaml new file mode 100644 index 000000000..387f55458 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/nodejs + services: + nodejs: + - serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: S2I + sourceType: + - Directory + targetOptions: + - m2kassets/s2i/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/service.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/service.yaml new file mode 100644 index 000000000..3d090ad31 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/incorrectservice/service.yaml @@ -0,0 +1,16 @@ +serviceName: nodejs +image: nodejs:latest +translationType: Any2Kube +containerBuildType: S2I +sourceType: + - Directory +targetOptions: + - m2kassets/s2i/php +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/container.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/container.yaml new file mode 100644 index 000000000..7a95b8910 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/container.yaml @@ -0,0 +1,25 @@ +imagenames: + - nodejs:latest +new: true +newfiles: + .s2i/environment: "" + nodejss2ibuild.sh: |- + # Copyright IBM Corporation 2020 + # + # 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. + + s2i build . registry.access.redhat.com/ubi8/nodejs-10 nodejs:latest +exposedports: + - 8080 +userid: -1 +accesseddirs: [] diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/plan.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/plan.yaml new file mode 100644 index 000000000..387f55458 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/plan.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: myproject +spec: + inputs: + rootDir: ../../samples/nodejs + services: + nodejs: + - serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: S2I + sourceType: + - Directory + targetOptions: + - m2kassets/s2i/nodejs + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/service.yaml b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/service.yaml new file mode 100644 index 000000000..49338b771 --- /dev/null +++ b/internal/containerizer/testdata/s2icontainerizer/getcontainer/normal/service.yaml @@ -0,0 +1,16 @@ +serviceName: nodejs +image: nodejs:latest +translationType: Any2Kube +containerBuildType: S2I +sourceType: + - Directory +targetOptions: + - m2kassets/s2i/nodejs +sourceArtifacts: + SourceCode: + - . +buildArtifacts: + SourceCode: + - . +updateContainerBuildPipeline: true +updateDeployPipeline: true diff --git a/internal/customizer/customizer.go b/internal/customizer/customizer.go new file mode 100644 index 000000000..3a69b013c --- /dev/null +++ b/internal/customizer/customizer.go @@ -0,0 +1,49 @@ +/* +Copyright IBM Corporation 2020 + +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 customizer + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" + log "github.com/sirupsen/logrus" +) + +//Customizer paramertizers the configuration +type customizer interface { + customize(ir *irtypes.IR) error +} + +//GetCustomizers gets the customizers registered with it +func getCustomizers() []customizer { + return []customizer{new(registryCustomizer), new(storageCustomizer)} +} + +//Customize invokes the customizes based on the customizer options +func Customize(ir irtypes.IR) (irtypes.IR, error) { + var customizers = getCustomizers() + log.Infoln("Begin Customization") + for _, c := range customizers { + log.Debugf("[%T] Begin Customization", c) + err := c.customize(&ir) + if err != nil { + log.Warnf("[%T] Failed : %s", c, err.Error()) + } else { + log.Debugf("[%T] Done", c) + } + } + log.Infoln("Customization done") + return ir, nil +} diff --git a/internal/customizer/registrycustomizer.go b/internal/customizer/registrycustomizer.go new file mode 100644 index 000000000..fa5f4e69a --- /dev/null +++ b/internal/customizer/registrycustomizer.go @@ -0,0 +1,289 @@ +/* +Copyright IBM Corporation 2020 + +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 customizer + +import ( + "bytes" + "fmt" + "net/url" + "strings" + + dockercliconfig "github.com/docker/cli/cli/config" + dockercliconfigfile "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/config/types" + dockerclitypes "github.com/docker/cli/cli/config/types" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/qaengine" + irtypes "github.com/konveyor/move2kube/internal/types" + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +const otherRegistry = "Other" + +// registryCustomizer customizes image registry related configurations +type registryCustomizer struct { +} + +// customize modifies image paths and secret +func (rc *registryCustomizer) customize(ir *irtypes.IR) error { + + usedRegistries := []string{} + registryList := []string{otherRegistry} + newimages := []string{} + + for _, container := range ir.Containers { + if container.New { + newimages = append(newimages, container.ImageNames...) + } + } + + for _, service := range ir.Services { + for _, container := range service.Containers { + if !common.IsStringPresent(newimages, container.Image) { + parts := strings.Split(container.Image, "/") + if len(parts) == 3 { + registryList = append(registryList, parts[0]) + usedRegistries = append(usedRegistries, parts[0]) + } + } + } + } + + registryAuthList := make(map[string]string) //Registry url and auth + defreg := "" + if !common.IgnoreEnvironment { + configFile, err := dockercliconfig.Load(dockercliconfig.Dir()) + if err == nil { + for regurl, regauth := range configFile.AuthConfigs { + u, err := url.Parse(regurl) + if err == nil { + if u.Host != "" { + regurl = u.Host + } + } + if regurl == "" { + continue + } + if !common.IsStringPresent(registryList, regurl) { + registryList = append(registryList, regurl) + } + if regauth.Auth != "" { + defreg = regurl + registryAuthList[regurl] = regauth.Auth + } + } + } + } + + if ir.Kubernetes.RegistryURL == "" && len(newimages) != 0 { + if !common.IsStringPresent(registryList, common.DefaultRegistryURL) { + registryList = append(registryList, common.DefaultRegistryURL) + } + if defreg == "" { + defreg = common.DefaultRegistryURL + } + problem, err := qatypes.NewSelectProblem("Select the registry where your images are hosted:", []string{"You can always change it later by changing the yamls."}, defreg, registryList) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + reg, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + if reg != otherRegistry { + ir.Kubernetes.RegistryURL = reg + } + } + + if ir.Kubernetes.RegistryURL == "" && len(newimages) != 0 { + problem, err := qatypes.NewInputProblem("Enter the name of the registry : ", []string{"Ex : " + common.DefaultRegistryURL}, common.DefaultRegistryURL) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + reg, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + if reg != "" { + ir.Kubernetes.RegistryURL = reg + } else { + ir.Kubernetes.RegistryURL = common.DefaultRegistryURL + } + } + + if ir.Kubernetes.RegistryNamespace == "" && len(newimages) != 0 { + problem, err := qatypes.NewInputProblem("Enter the namespace where the new images are pushed : ", []string{"Ex : " + ir.Name}, ir.Name) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + ns, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + if ns != "" { + ir.Kubernetes.RegistryNamespace = ns + } else { + ir.Kubernetes.RegistryNamespace = ir.Name + } + } + + if !common.IsStringPresent(usedRegistries, ir.Kubernetes.RegistryURL) { + usedRegistries = append(usedRegistries, ir.Kubernetes.RegistryURL) + } + + imagePullSecrets := make(map[string]string) // registryurl, pull secret + + for _, registry := range usedRegistries { + dauth := dockerclitypes.AuthConfig{} + const dockerConfigLogin = "Docker login from config" + const noAuthLogin = "No authentication" + const userLogin = "UserName/Password" + const useExistingPullSecret = "Use existing pull secret" + authOptions := []string{useExistingPullSecret, noAuthLogin, userLogin} + if auth, ok := registryAuthList[ir.Kubernetes.RegistryURL]; ok { + imagePullSecrets[registry] = common.ImagePullSecretPrefix + common.MakeFileNameCompliant(imagePullSecrets[registry]) + dauth.Auth = auth + authOptions = append(authOptions, dockerConfigLogin) + } + + problem, err := qatypes.NewSelectProblem(fmt.Sprintf("[%s] What type of container registry login do you want to use?", registry), []string{"Docker login from config mode, will use the default config from your local machine."}, noAuthLogin, authOptions) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + auth, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + if auth == noAuthLogin { + dauth.Auth = "" + } else if auth == useExistingPullSecret { + problem, err := qatypes.NewInputProblem(fmt.Sprintf("[%s] Enter the name of the pull secret : ", registry), []string{"The pull secret should exist in the namespace where you will be deploying the application."}, "") + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + ps, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + imagePullSecrets[registry] = ps + } else if auth != dockerConfigLogin { + problem, err := qatypes.NewInputProblem(fmt.Sprintf("[%s] Enter the container registry username : ", registry), []string{"Enter username for container registry login"}, "iamapikey") + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + un, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + dauth.Username = un + problem, err = qatypes.NewPasswordProblem(fmt.Sprintf("[%s] Enter the container registry password : ", registry), []string{"Enter password for container registry login."}) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + pwd, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + dauth.Password = pwd + } + if dauth != (types.AuthConfig{}) { + dconfigfile := dockercliconfigfile.ConfigFile{ + AuthConfigs: map[string]dockerclitypes.AuthConfig{ir.Kubernetes.RegistryURL: dauth}, + } + dconfigbuffer := new(bytes.Buffer) + err := dconfigfile.SaveToWriter(dconfigbuffer) + if err == nil { + data := make(map[string][]byte) + data[".dockerconfigjson"] = dconfigbuffer.Bytes() + ir.AddStorage(irtypes.Storage{ + Name: imagePullSecrets[registry], + StorageType: irtypes.PullSecretKind, + Content: data, + }) + } else { + log.Warnf("Unable to create auth string : %s", err) + } + } + } + + ir.Values.RegistryNamespace = ir.Kubernetes.RegistryNamespace + ir.Values.RegistryURL = ir.Kubernetes.RegistryURL + for _, service := range ir.Services { + for i, serviceContainer := range service.Containers { + if common.IsStringPresent(newimages, serviceContainer.Image) { + parts := strings.Split(serviceContainer.Image, "/") + image, tag := common.GetImageNameAndTag(parts[len(parts)-1]) + if ir.Kubernetes.RegistryURL != "" && ir.Kubernetes.RegistryNamespace != "" { + serviceContainer.Image = ir.Kubernetes.RegistryURL + "/" + ir.Kubernetes.RegistryNamespace + "/" + image + ":" + tag + } else if ir.Kubernetes.RegistryNamespace != "" { + serviceContainer.Image = ir.Kubernetes.RegistryNamespace + "/" + image + ":" + tag + } else { + serviceContainer.Image = image + ":" + tag + } + service.Containers[i] = serviceContainer + } + parts := strings.Split(serviceContainer.Image, "/") + if len(parts) == 3 { + reg := parts[0] + if ps, ok := imagePullSecrets[reg]; ok { + found := false + for _, eps := range service.ImagePullSecrets { + if eps.Name == ps { + found = true + } + } + if !found { + service.ImagePullSecrets = append(service.ImagePullSecrets, corev1.LocalObjectReference{Name: ps}) + } + } + } + } + ir.Services[service.Name] = service + } + return nil +} diff --git a/internal/customizer/storagecustomizer.go b/internal/customizer/storagecustomizer.go new file mode 100644 index 000000000..81ec173fb --- /dev/null +++ b/internal/customizer/storagecustomizer.go @@ -0,0 +1,213 @@ +/* +Copyright IBM Corporation 2020 + +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 customizer + +import ( + "fmt" + "path/filepath" + + log "github.com/sirupsen/logrus" + + corev1 "k8s.io/api/core/v1" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/qaengine" + irtypes "github.com/konveyor/move2kube/internal/types" + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +//storageCustomizer customizes storage +type storageCustomizer struct { + ir *irtypes.IR +} + +const ( + alloption string = "Apply for all" +) + +//customize customizes the storage +func (ic *storageCustomizer) customize(ir *irtypes.IR) error { + ic.ir = ir + ic.convertHostPathToPVC() + + if len(ir.Storages) == 0 { + log.Debugf("Empty storage list. Nothing to customize.") + return nil + } + if ir.TargetClusterSpec.StorageClasses == nil || len(ir.TargetClusterSpec.StorageClasses) == 0 { + s := "No storage classes available in the cluster" + log.Warnf(s) + return fmt.Errorf(s) + } + claimSvcMap := ic.getPVCs() + + if len(claimSvcMap) == 0 { + log.Debugf("No service with volumes detected. Storage class configuration not required.") + return nil + } + + selectedKeys := []string{} + for k := range claimSvcMap { + selectedKeys = append(selectedKeys, k) + } + + if len(selectedKeys) > 1 { + if !ic.shouldConfigureSeparately(selectedKeys) { + storageClass := ic.selectStorageClass(ir.TargetClusterSpec.StorageClasses, alloption, []string{}) + for _, storage := range ir.Storages { + if storage.StorageType == irtypes.PVCKind { + storage.PersistentVolumeClaimSpec.StorageClassName = &storageClass + } + } + return nil + } + } + + for i, s := range ir.Storages { + if svs, ok := claimSvcMap[s.Name]; ok { + storageClassName := ic.selectStorageClass(ir.TargetClusterSpec.StorageClasses, s.Name, svs) + s.StorageClassName = &storageClassName + ir.Storages[i] = s + } + } + + return nil +} + +func (ic *storageCustomizer) convertHostPathToPVC() { + hostPathsVisited := make(map[string]string) + for _, service := range ic.ir.Services { + log.Debugf("Service %s has %d volumes", service.Name, len(service.Volumes)) + for vi, v := range service.Volumes { + if v.HostPath != nil { + if name, ok := hostPathsVisited[v.HostPath.Path]; ok { + hostPathsVisited[v.HostPath.Path] = "" + log.Debugf("Detected host path [%s]", v.HostPath.Path) + if !ic.shouldHostPathBeRetained(v.HostPath.Path) { + hostPathsVisited[v.HostPath.Path] = v.Name + v.VolumeSource = corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: v.Name, + }} + service.Volumes[vi] = v + ic.ir.Services[service.Name] = service + storageObj := irtypes.Storage{ + StorageType: irtypes.PVCKind, + Name: v.Name, + PersistentVolumeClaimSpec: corev1.PersistentVolumeClaimSpec{ + VolumeName: v.Name, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: common.DefaultPVCSize, + }, + }, + }} + ic.ir.AddStorage(storageObj) + } + } else { + v.VolumeSource = corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: name, + }} + service.Volumes[vi] = v + ic.ir.Services[service.Name] = service + } + } + } + } +} + +func (ic storageCustomizer) shouldHostPathBeRetained(hostPath string) bool { + if filepath.IsAbs(hostPath) { + return true + } + + problem, err := qatypes.NewConfirmProblem(fmt.Sprintf("Do you want to create PVC for host path [%s]?:", hostPath), []string{"Use PVC for persistent storage wherever applicable"}, false) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + ans, err := problem.GetBoolAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + return !ans +} + +func (ic storageCustomizer) shouldConfigureSeparately(claims []string) bool { + context := make([]string, 2) + context[0] = "Storage classes have to be configured for below claims:" + context[1] = fmt.Sprintf("%+v", claims) + + problem, err := qatypes.NewConfirmProblem("Do you want to configure different storage classes for each claim?", context, false) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + ans, err := problem.GetBoolAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + return ans +} + +func (ic storageCustomizer) selectStorageClass(storageClasses []string, claimName string, services []string) string { + var desc string + if claimName == alloption { + desc = "Which storage class to use for all persistent volume claims?" + } else { + desc = fmt.Sprintf("Which storage class to use for persistent volume claim [%s] used by %+v", claimName, services) + } + problem, err := qatypes.NewSelectProblem(desc, []string{"If you have a custom cluster, you can use collect to get storage classes from it."}, storageClasses[0], storageClasses) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + sc, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + return sc +} + +func (ic *storageCustomizer) getPVCs() map[string][]string { + pvcmap := make(map[string][]string) + for _, s := range ic.ir.Storages { + if s.StorageType == irtypes.PVCKind { + svcList := []string{} + for svcName, svc := range ic.ir.Services { + for _, v := range svc.Volumes { + if v.Name == s.Name { + svcList = append(svcList, svcName) + break + } + } + } + pvcmap[s.Name] = svcList + } + } + return pvcmap +} diff --git a/internal/metadata/clustermdloader.go b/internal/metadata/clustermdloader.go new file mode 100644 index 000000000..1bda2823a --- /dev/null +++ b/internal/metadata/clustermdloader.go @@ -0,0 +1,93 @@ +/* +Copyright IBM Corporation 2020 + +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 metadata + +import ( + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + + common "github.com/konveyor/move2kube/internal/common" + clustersmetadata "github.com/konveyor/move2kube/internal/metadata/clusters" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +//go:generate go run github.com/konveyor/move2kube/internal/common/generator clusters makemaps + +// ClusterMDLoader Implements Loader interface +type ClusterMDLoader struct { +} + +// UpdatePlan - output a plan based on the input directory contents +func (i ClusterMDLoader) UpdatePlan(inputPath string, plan *plantypes.Plan) error { + files, err := common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize cluster metadata yamls : %s", err) + } + for _, path := range files { + cm := new(collecttypes.ClusterMetadata) + if common.ReadYaml(path, &cm) == nil && cm.Kind == string(collecttypes.ClusterMetadataKind) { + relpath, _ := plan.GetRelativePath(path) + plan.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType] = append(plan.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType], relpath) + if plan.Spec.Outputs.Kubernetes.ClusterType == common.DefaultClusterType { + plan.Spec.Outputs.Kubernetes.ClusterType = cm.Name + } + + //If there is a cluster-metadata available from collect, then set below flag to true + plan.Spec.Outputs.Kubernetes.IgnoreUnsupportedKinds = true + } + } + return nil +} + +// LoadToIR loads target cluster in IR +func (i ClusterMDLoader) LoadToIR(p plantypes.Plan, ir *irtypes.IR) error { + clusters := i.GetClusters(p) + ir.TargetClusterSpec = clusters[p.Spec.Outputs.Kubernetes.ClusterType].Spec + return nil +} + +// GetClusters loads list of clusters +func (i ClusterMDLoader) GetClusters(p plantypes.Plan) map[string]collecttypes.ClusterMetadata { + clusters := make(map[string]collecttypes.ClusterMetadata) + for fname, clustermd := range clustersmetadata.Constants { + cm := collecttypes.ClusterMetadata{} + err := yaml.Unmarshal([]byte(clustermd), &cm) + if err != nil { + log.Warnf("Unable to marshal inbuilt cluster info : %s", fname) + continue + } + if len(cm.Spec.StorageClasses) == 0 { + cm.Spec.StorageClasses = []string{common.DefaultStorageClassName} + log.Debugf("No storage class in the cluster, adding [default]") + } + clusters[cm.Name] = cm + } + for _, clusterfilepath := range p.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType] { + cm := new(collecttypes.ClusterMetadata) + if common.ReadYaml(p.GetFullPath(clusterfilepath), &cm) == nil && cm.Kind == string(collecttypes.ClusterMetadataKind) { + if len(cm.Spec.StorageClasses) == 0 { + cm.Spec.StorageClasses = []string{common.DefaultStorageClassName} + log.Debugf("No storage class in the cluster, adding [default]") + } + clusters[cm.Name] = *cm + clusters[clusterfilepath] = *cm + } + } + return clusters +} diff --git a/internal/metadata/clustermdloader_test.go b/internal/metadata/clustermdloader_test.go new file mode 100644 index 000000000..080676395 --- /dev/null +++ b/internal/metadata/clustermdloader_test.go @@ -0,0 +1,182 @@ +/* +Copyright IBM Corporation 2020 + +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 metadata_test + +import ( + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/metadata" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +func TestUpdatePlan(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("update plan when there are no files", func(t *testing.T) { + // Setup + p := plantypes.NewPlan() + want := plantypes.NewPlan() + loader := metadata.ClusterMDLoader{} + inputPath := t.TempDir() + + // Test + if err := loader.UpdatePlan(inputPath, &p); err != nil { + t.Fatal("Failed to update the plan. Error:", err) + } + if !reflect.DeepEqual(p, want) { + t.Fatal("The updated plan is incorrect. Expected", want, "Actual:", p) + } + }) + + t.Run("update plan with some empty files", func(t *testing.T) { + // Setup + p := plantypes.NewPlan() + want := plantypes.NewPlan() + loader := metadata.ClusterMDLoader{} + inputPath := "testdata/emptyfiles" + + // Test + if err := loader.UpdatePlan(inputPath, &p); err != nil { + t.Fatal("Failed to update the plan. Error:", err) + } + if !reflect.DeepEqual(p, want) { + t.Fatal("The updated plan is incorrect. Expected", want, "Actual:", p) + } + }) + + t.Run("update plan with some invalid files", func(t *testing.T) { + // Setup + p := plantypes.NewPlan() + want := plantypes.NewPlan() + loader := metadata.ClusterMDLoader{} + inputPath := "testdata/invalidfiles" + + // Test + if err := loader.UpdatePlan(inputPath, &p); err != nil { + t.Fatal("Failed to update the plan. Error:", err) + } + if !reflect.DeepEqual(p, want) { + t.Fatal("The updated plan is incorrect. Expected", want, "Actual:", p) + } + }) + + t.Run("update plan with some valid files", func(t *testing.T) { + // Setup + p := plantypes.NewPlan() + want := plantypes.NewPlan() + want.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType] = []string{"testdata/validfiles/test1.yaml", "testdata/validfiles/test2.yml"} + want.Spec.Outputs.Kubernetes.ClusterType = "name1" + want.Spec.Outputs.Kubernetes.IgnoreUnsupportedKinds = true + loader := metadata.ClusterMDLoader{} + inputPath := "testdata/validfiles" + + // Test + if err := loader.UpdatePlan(inputPath, &p); err != nil { + t.Fatal("Failed to update the plan. Error:", err) + } + if !reflect.DeepEqual(p, want) { + t.Fatal("The updated plan is incorrect. Expected", want, "Actual:", p) + } + }) +} + +func TestLoadToIR(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("load IR with an empty plan", func(t *testing.T) { + p := plantypes.NewPlan() + ir := irtypes.NewIR(p) + loader := metadata.ClusterMDLoader{} + if err := loader.LoadToIR(p, &ir); err != nil { + t.Fatal("Failed to load IR. Error:", err) + } + }) +} + +func TestGetClusters(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get clusters from an empty plan", func(t *testing.T) { + p := plantypes.NewPlan() + loader := metadata.ClusterMDLoader{} + cmMap := loader.GetClusters(p) + if _, ok := cmMap["Kubernetes"]; !ok { + t.Fatal("Missing builtin kubernetes cluster metadata. The returned cluster info:", cmMap) + } + if _, ok := cmMap["Openshift"]; !ok { + t.Fatal("Missing builtin openshift cluster metadata. The returned cluster info:", cmMap) + } + for k, v := range cmMap { + if v.Kind != string(collecttypes.ClusterMetadataKind) { + t.Fatal("The kind is incorrect for key", k, "Expected:", collecttypes.ClusterMetadataKind, " Actual:", v.Kind) + } else if k != v.Name { + t.Fatal("The cluster metadata was inserted under incorrect key. Expected:", v.Name, "Actual:", k) + } else if len(v.Spec.StorageClasses) == 0 { + t.Fatal("There are no storage classes in the cluster metadata. Excpected there to be at least 'default' storage class. Actual:", v.Spec.StorageClasses) + } + } + }) + + t.Run("get clusters from a filled plan", func(t *testing.T) { + p := plantypes.NewPlan() + p.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType] = []string{"testdata/validfiles/test1.yaml", "testdata/validfiles/test2.yml"} + loader := metadata.ClusterMDLoader{} + cmMap := loader.GetClusters(p) + if _, ok := cmMap["Kubernetes"]; !ok { + t.Fatal("Missing builtin kubernetes cluster metadata. The returned cluster info:", cmMap) + } + if _, ok := cmMap["Openshift"]; !ok { + t.Fatal("Missing builtin openshift cluster metadata. The returned cluster info:", cmMap) + } + for k, v := range cmMap { + if v.Kind != string(collecttypes.ClusterMetadataKind) { + t.Fatal("The kind is incorrect for key", k, "Expected:", collecttypes.ClusterMetadataKind, " Actual:", v.Kind) + } else if k != v.Name && !((k == "testdata/validfiles/test1.yaml" || k == "testdata/validfiles/test2.yml") && v.Name == "name1") { + t.Fatal("The cluster metadata was inserted under incorrect key. Expected the key to be either the context name", v.Name, "or the file path. Actual:", k) + } else if len(v.Spec.StorageClasses) == 0 { + t.Fatal("There are no storage classes in the cluster metadata. Excpected there to be at least 'default' storage class. Actual:", v.Spec.StorageClasses) + } + } + }) + + t.Run("get clusters from a filled plan", func(t *testing.T) { + p := plantypes.NewPlan() + p.Spec.Inputs.TargetInfoArtifacts[plantypes.K8sClusterArtifactType] = []string{"testdata/validfilesnostorageclasses/test1.yaml", "testdata/validfilesnostorageclasses/test2.yml"} + loader := metadata.ClusterMDLoader{} + cmMap := loader.GetClusters(p) + if _, ok := cmMap["Kubernetes"]; !ok { + t.Fatal("Missing builtin kubernetes cluster metadata. The returned cluster info:", cmMap) + } + if _, ok := cmMap["Openshift"]; !ok { + t.Fatal("Missing builtin openshift cluster metadata. The returned cluster info:", cmMap) + } + for k, v := range cmMap { + if v.Kind != string(collecttypes.ClusterMetadataKind) { + t.Fatal("The kind is incorrect for key", k, "Expected:", collecttypes.ClusterMetadataKind, " Actual:", v.Kind) + } else if k != v.Name && !((k == "testdata/validfilesnostorageclasses/test1.yaml" || k == "testdata/validfilesnostorageclasses/test2.yml") && v.Name == "name1") { + t.Fatal("The cluster metadata was inserted under incorrect key. Expected the key to be either the context name", v.Name, "or the file path. Actual:", k) + } else if len(v.Spec.StorageClasses) == 0 { + t.Fatal("There are no storage classes in the cluster metadata. Excpected there to be at least 'default' storage class. Actual:", v.Spec.StorageClasses) + } + } + }) +} diff --git a/internal/metadata/clusters/constants.go b/internal/metadata/clusters/constants.go new file mode 100644 index 000000000..dff698d68 --- /dev/null +++ b/internal/metadata/clusters/constants.go @@ -0,0 +1,482 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2020-09-14 15:52:47.281083 +0530 IST m=+0.001006524 + +/* +Copyright IBM Corporation 2020 + +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 clusters + +var Constants= map[string]string{ + + `kubernetes_yaml` : `apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: Kubernetes +spec: + storageClasses: + - default + - ibmc-block-bronze + - ibmc-block-custom + - ibmc-block-gold + - ibmc-block-retain-bronze + - ibmc-block-retain-custom + - ibmc-block-retain-gold + - ibmc-block-retain-silver + - ibmc-block-silver + - ibmc-file-bronze + - ibmc-file-bronze-gid + - ibmc-file-custom + - ibmc-file-gold + - ibmc-file-gold-gid + - ibmc-file-retain-bronze + - ibmc-file-retain-custom + - ibmc-file-retain-gold + - ibmc-file-retain-silver + - ibmc-file-silver + - ibmc-file-silver-gid + apiKindVersionMap: + APIService: + - apiregistration.k8s.io/v1 + Binding: + - v1 + CSIDriver: + - storage.k8s.io/v1beta1 + CSINode: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + CatalogSource: + - operators.coreos.com/v1alpha1 + CertificateSigningRequest: + - certificates.k8s.io/v1beta1 + ClusterImagePolicy: + - securityenforcement.admission.cloud.ibm.com/v1beta1 + ClusterRole: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterRoleBinding: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterServiceVersion: + - operators.coreos.com/v1alpha1 + ComponentStatus: + - v1 + ConfigMap: + - v1 + ControllerRevision: + - apps/v1 + CronJob: + - batch/v1beta1 + - batch/v2alpha1 + CustomResourceDefinition: + - apiextensions.k8s.io/v1 + DaemonSet: + - apps/v1 + Deployment: + - apps/v1 + EndpointSlice: + - discovery.k8s.io/v1beta1 + Endpoints: + - v1 + Event: + - events.k8s.io/v1beta1 + - v1 + HorizontalPodAutoscaler: + - autoscaling/v1 + - autoscaling/v2beta1 + - autoscaling/v2beta2 + ImagePolicy: + - securityenforcement.admission.cloud.ibm.com/v1beta1 + Ingress: + - networking.k8s.io/v1beta1 + - extensions/v1beta1 + InstallPlan: + - operators.coreos.com/v1alpha1 + Job: + - batch/v1 + Lease: + - coordination.k8s.io/v1beta1 + - coordination.k8s.io/v1 + LimitRange: + - v1 + LocalSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + MutatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + - admissionregistration.k8s.io/v1 + Namespace: + - v1 + NetworkPolicy: + - networking.k8s.io/v1 + Node: + - v1 + OperatorGroup: + - operators.coreos.com/v1 + PersistentVolume: + - v1 + PersistentVolumeClaim: + - v1 + Pod: + - v1 + PodDisruptionBudget: + - policy/v1beta1 + PodSecurityPolicy: + - policy/v1beta1 + PodTemplate: + - v1 + PriorityClass: + - scheduling.k8s.io/v1beta1 + - scheduling.k8s.io/v1 + ReplicaSet: + - apps/v1 + ReplicationController: + - v1 + ResourceQuota: + - v1 + Role: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBinding: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + Secret: + - v1 + SelfSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SelfSubjectRulesReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Service: + - v1 + ServiceAccount: + - v1 + StatefulSet: + - apps/v1 + StorageClass: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + SubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Subscription: + - operators.coreos.com/v1alpha1 + TokenReview: + - authentication.k8s.io/v1 + - authentication.k8s.io/v1beta1 + ValidatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + - admissionregistration.k8s.io/v1 + VolumeAttachment: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 +`, + + `openshift_yaml` : `apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: Openshift +spec: + storageClasses: + - default + - ibmc-block-bronze + - ibmc-block-custom + - ibmc-block-gold + - ibmc-block-retain-bronze + - ibmc-block-retain-custom + - ibmc-block-retain-gold + - ibmc-block-retain-silver + - ibmc-block-silver + - ibmc-file-bronze + - ibmc-file-bronze-gid + - ibmc-file-custom + - ibmc-file-gold + - ibmc-file-gold-gid + - ibmc-file-retain-bronze + - ibmc-file-retain-custom + - ibmc-file-retain-gold + - ibmc-file-retain-silver + - ibmc-file-silver + - ibmc-file-silver-gid + apiKindVersionMap: + APIService: + - apiregistration.k8s.io/v1 + - apiregistration.k8s.io/v1beta1 + Alertmanager: + - monitoring.coreos.com/v1 + AppliedClusterResourceQuota: + - quota.openshift.io/v1 + BinaryBuildRequestOptions: + - build.openshift.io/v1 + Binding: + - v1 + BrokerTemplateInstance: + - template.openshift.io/v1 + Build: + - build.openshift.io/v1 + BuildConfig: + - build.openshift.io/v1 + BuildLog: + - build.openshift.io/v1 + BuildRequest: + - build.openshift.io/v1 + Bundle: + - automationbroker.io/v1alpha1 + BundleBinding: + - automationbroker.io/v1alpha1 + BundleInstance: + - automationbroker.io/v1alpha1 + CertificateSigningRequest: + - certificates.k8s.io/v1beta1 + ClusterNetwork: + - network.openshift.io/v1 + ClusterResourceQuota: + - quota.openshift.io/v1 + ClusterRole: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterRoleBinding: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterServiceBroker: + - servicecatalog.k8s.io/v1beta1 + ClusterServiceClass: + - servicecatalog.k8s.io/v1beta1 + ClusterServicePlan: + - servicecatalog.k8s.io/v1beta1 + ComponentStatus: + - v1 + ConfigMap: + - v1 + ControllerRevision: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + CronJob: + - batch/v1beta1 + CustomResourceDefinition: + - apiextensions.k8s.io/v1beta1 + DaemonSet: + - apps/v1 + - apps/v1beta2 + - extensions/v1beta1 + Deployment: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - extensions/v1beta1 + DeploymentConfig: + - apps.openshift.io/v1 + DeploymentConfigRollback: + - apps.openshift.io/v1 + DeploymentLog: + - apps.openshift.io/v1 + DeploymentRequest: + - apps.openshift.io/v1 + DeploymentRollback: + - apps/v1beta1 + - extensions/v1beta1 + EgressNetworkPolicy: + - network.openshift.io/v1 + Endpoints: + - v1 + Event: + - events.k8s.io/v1beta1 + - v1 + Eviction: + - v1 + Group: + - user.openshift.io/v1 + HorizontalPodAutoscaler: + - autoscaling/v1 + - autoscaling/v2beta1 + HostSubnet: + - network.openshift.io/v1 + Identity: + - user.openshift.io/v1 + Image: + - image.openshift.io/v1 + ImageSignature: + - image.openshift.io/v1 + ImageStream: + - image.openshift.io/v1 + ImageStreamImage: + - image.openshift.io/v1 + ImageStreamImport: + - image.openshift.io/v1 + ImageStreamLayers: + - image.openshift.io/v1 + ImageStreamMapping: + - image.openshift.io/v1 + ImageStreamTag: + - image.openshift.io/v1 + Ingress: + - extensions/v1beta1 + Job: + - batch/v1 + LimitRange: + - v1 + LocalResourceAccessReview: + - authorization.openshift.io/v1 + LocalSubjectAccessReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + MutatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + Namespace: + - v1 + NetNamespace: + - network.openshift.io/v1 + NetworkPolicy: + - networking.k8s.io/v1 + - extensions/v1beta1 + Node: + - v1 + OAuthAccessToken: + - oauth.openshift.io/v1 + OAuthAuthorizeToken: + - oauth.openshift.io/v1 + OAuthClient: + - oauth.openshift.io/v1 + OAuthClientAuthorization: + - oauth.openshift.io/v1 + PersistentVolume: + - v1 + PersistentVolumeClaim: + - v1 + Pod: + - v1 + PodDisruptionBudget: + - policy/v1beta1 + PodSecurityPolicy: + - extensions/v1beta1 + - policy/v1beta1 + PodSecurityPolicyReview: + - security.openshift.io/v1 + PodSecurityPolicySelfSubjectReview: + - security.openshift.io/v1 + PodSecurityPolicySubjectReview: + - security.openshift.io/v1 + PodTemplate: + - v1 + PriorityClass: + - scheduling.k8s.io/v1beta1 + Project: + - project.openshift.io/v1 + ProjectRequest: + - project.openshift.io/v1 + Prometheus: + - monitoring.coreos.com/v1 + PrometheusRule: + - monitoring.coreos.com/v1 + RangeAllocation: + - security.openshift.io/v1 + ReplicaSet: + - apps/v1 + - apps/v1beta2 + - extensions/v1beta1 + ReplicationController: + - v1 + ReplicationControllerDummy: + - extensions/v1beta1 + ResourceAccessReview: + - authorization.openshift.io/v1 + ResourceQuota: + - v1 + Role: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBinding: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBindingRestriction: + - authorization.openshift.io/v1 + Route: + - route.openshift.io/v1 + Scale: + - apps.openshift.io/v1 + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - extensions/v1beta1 + - v1 + Secret: + - v1 + SecretList: + - image.openshift.io/v1 + SecurityContextConstraints: + - security.openshift.io/v1 + - v1 + SelfSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SelfSubjectRulesReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Service: + - v1 + ServiceAccount: + - v1 + ServiceBinding: + - servicecatalog.k8s.io/v1beta1 + ServiceBroker: + - servicecatalog.k8s.io/v1beta1 + ServiceClass: + - servicecatalog.k8s.io/v1beta1 + ServiceInstance: + - servicecatalog.k8s.io/v1beta1 + ServiceMonitor: + - monitoring.coreos.com/v1 + ServicePlan: + - servicecatalog.k8s.io/v1beta1 + StatefulSet: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + StorageClass: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + SubjectAccessReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SubjectRulesReview: + - authorization.openshift.io/v1 + Template: + - template.openshift.io/v1 + TemplateInstance: + - template.openshift.io/v1 + TokenReview: + - authentication.k8s.io/v1 + - authentication.k8s.io/v1beta1 + User: + - user.openshift.io/v1 + UserIdentityMapping: + - user.openshift.io/v1 + ValidatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + VolumeAttachment: + - storage.k8s.io/v1beta1 +`, + +} \ No newline at end of file diff --git a/internal/metadata/clusters/kubernetes.yaml b/internal/metadata/clusters/kubernetes.yaml new file mode 100644 index 000000000..035cb2bc8 --- /dev/null +++ b/internal/metadata/clusters/kubernetes.yaml @@ -0,0 +1,162 @@ +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: Kubernetes +spec: + storageClasses: + - default + - ibmc-block-bronze + - ibmc-block-custom + - ibmc-block-gold + - ibmc-block-retain-bronze + - ibmc-block-retain-custom + - ibmc-block-retain-gold + - ibmc-block-retain-silver + - ibmc-block-silver + - ibmc-file-bronze + - ibmc-file-bronze-gid + - ibmc-file-custom + - ibmc-file-gold + - ibmc-file-gold-gid + - ibmc-file-retain-bronze + - ibmc-file-retain-custom + - ibmc-file-retain-gold + - ibmc-file-retain-silver + - ibmc-file-silver + - ibmc-file-silver-gid + apiKindVersionMap: + APIService: + - apiregistration.k8s.io/v1 + Binding: + - v1 + CSIDriver: + - storage.k8s.io/v1beta1 + CSINode: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + CatalogSource: + - operators.coreos.com/v1alpha1 + CertificateSigningRequest: + - certificates.k8s.io/v1beta1 + ClusterImagePolicy: + - securityenforcement.admission.cloud.ibm.com/v1beta1 + ClusterRole: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterRoleBinding: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterServiceVersion: + - operators.coreos.com/v1alpha1 + ComponentStatus: + - v1 + ConfigMap: + - v1 + ControllerRevision: + - apps/v1 + CronJob: + - batch/v1beta1 + - batch/v2alpha1 + CustomResourceDefinition: + - apiextensions.k8s.io/v1 + DaemonSet: + - apps/v1 + Deployment: + - apps/v1 + EndpointSlice: + - discovery.k8s.io/v1beta1 + Endpoints: + - v1 + Event: + - events.k8s.io/v1beta1 + - v1 + HorizontalPodAutoscaler: + - autoscaling/v1 + - autoscaling/v2beta1 + - autoscaling/v2beta2 + ImagePolicy: + - securityenforcement.admission.cloud.ibm.com/v1beta1 + Ingress: + - networking.k8s.io/v1beta1 + - extensions/v1beta1 + InstallPlan: + - operators.coreos.com/v1alpha1 + Job: + - batch/v1 + Lease: + - coordination.k8s.io/v1beta1 + - coordination.k8s.io/v1 + LimitRange: + - v1 + LocalSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + MutatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + - admissionregistration.k8s.io/v1 + Namespace: + - v1 + NetworkPolicy: + - networking.k8s.io/v1 + Node: + - v1 + OperatorGroup: + - operators.coreos.com/v1 + PersistentVolume: + - v1 + PersistentVolumeClaim: + - v1 + Pod: + - v1 + PodDisruptionBudget: + - policy/v1beta1 + PodSecurityPolicy: + - policy/v1beta1 + PodTemplate: + - v1 + PriorityClass: + - scheduling.k8s.io/v1beta1 + - scheduling.k8s.io/v1 + ReplicaSet: + - apps/v1 + ReplicationController: + - v1 + ResourceQuota: + - v1 + Role: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBinding: + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + Secret: + - v1 + SelfSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SelfSubjectRulesReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Service: + - v1 + ServiceAccount: + - v1 + StatefulSet: + - apps/v1 + StorageClass: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + SubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Subscription: + - operators.coreos.com/v1alpha1 + TokenReview: + - authentication.k8s.io/v1 + - authentication.k8s.io/v1beta1 + ValidatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + - admissionregistration.k8s.io/v1 + VolumeAttachment: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 diff --git a/internal/metadata/clusters/openshift.yaml b/internal/metadata/clusters/openshift.yaml new file mode 100644 index 000000000..88dd95569 --- /dev/null +++ b/internal/metadata/clusters/openshift.yaml @@ -0,0 +1,291 @@ +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: Openshift +spec: + storageClasses: + - default + - ibmc-block-bronze + - ibmc-block-custom + - ibmc-block-gold + - ibmc-block-retain-bronze + - ibmc-block-retain-custom + - ibmc-block-retain-gold + - ibmc-block-retain-silver + - ibmc-block-silver + - ibmc-file-bronze + - ibmc-file-bronze-gid + - ibmc-file-custom + - ibmc-file-gold + - ibmc-file-gold-gid + - ibmc-file-retain-bronze + - ibmc-file-retain-custom + - ibmc-file-retain-gold + - ibmc-file-retain-silver + - ibmc-file-silver + - ibmc-file-silver-gid + apiKindVersionMap: + APIService: + - apiregistration.k8s.io/v1 + - apiregistration.k8s.io/v1beta1 + Alertmanager: + - monitoring.coreos.com/v1 + AppliedClusterResourceQuota: + - quota.openshift.io/v1 + BinaryBuildRequestOptions: + - build.openshift.io/v1 + Binding: + - v1 + BrokerTemplateInstance: + - template.openshift.io/v1 + Build: + - build.openshift.io/v1 + BuildConfig: + - build.openshift.io/v1 + BuildLog: + - build.openshift.io/v1 + BuildRequest: + - build.openshift.io/v1 + Bundle: + - automationbroker.io/v1alpha1 + BundleBinding: + - automationbroker.io/v1alpha1 + BundleInstance: + - automationbroker.io/v1alpha1 + CertificateSigningRequest: + - certificates.k8s.io/v1beta1 + ClusterNetwork: + - network.openshift.io/v1 + ClusterResourceQuota: + - quota.openshift.io/v1 + ClusterRole: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterRoleBinding: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + ClusterServiceBroker: + - servicecatalog.k8s.io/v1beta1 + ClusterServiceClass: + - servicecatalog.k8s.io/v1beta1 + ClusterServicePlan: + - servicecatalog.k8s.io/v1beta1 + ComponentStatus: + - v1 + ConfigMap: + - v1 + ControllerRevision: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + CronJob: + - batch/v1beta1 + CustomResourceDefinition: + - apiextensions.k8s.io/v1beta1 + DaemonSet: + - apps/v1 + - apps/v1beta2 + - extensions/v1beta1 + Deployment: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - extensions/v1beta1 + DeploymentConfig: + - apps.openshift.io/v1 + DeploymentConfigRollback: + - apps.openshift.io/v1 + DeploymentLog: + - apps.openshift.io/v1 + DeploymentRequest: + - apps.openshift.io/v1 + DeploymentRollback: + - apps/v1beta1 + - extensions/v1beta1 + EgressNetworkPolicy: + - network.openshift.io/v1 + Endpoints: + - v1 + Event: + - events.k8s.io/v1beta1 + - v1 + Eviction: + - v1 + Group: + - user.openshift.io/v1 + HorizontalPodAutoscaler: + - autoscaling/v1 + - autoscaling/v2beta1 + HostSubnet: + - network.openshift.io/v1 + Identity: + - user.openshift.io/v1 + Image: + - image.openshift.io/v1 + ImageSignature: + - image.openshift.io/v1 + ImageStream: + - image.openshift.io/v1 + ImageStreamImage: + - image.openshift.io/v1 + ImageStreamImport: + - image.openshift.io/v1 + ImageStreamLayers: + - image.openshift.io/v1 + ImageStreamMapping: + - image.openshift.io/v1 + ImageStreamTag: + - image.openshift.io/v1 + Ingress: + - extensions/v1beta1 + Job: + - batch/v1 + LimitRange: + - v1 + LocalResourceAccessReview: + - authorization.openshift.io/v1 + LocalSubjectAccessReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + MutatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + Namespace: + - v1 + NetNamespace: + - network.openshift.io/v1 + NetworkPolicy: + - networking.k8s.io/v1 + - extensions/v1beta1 + Node: + - v1 + OAuthAccessToken: + - oauth.openshift.io/v1 + OAuthAuthorizeToken: + - oauth.openshift.io/v1 + OAuthClient: + - oauth.openshift.io/v1 + OAuthClientAuthorization: + - oauth.openshift.io/v1 + PersistentVolume: + - v1 + PersistentVolumeClaim: + - v1 + Pod: + - v1 + PodDisruptionBudget: + - policy/v1beta1 + PodSecurityPolicy: + - extensions/v1beta1 + - policy/v1beta1 + PodSecurityPolicyReview: + - security.openshift.io/v1 + PodSecurityPolicySelfSubjectReview: + - security.openshift.io/v1 + PodSecurityPolicySubjectReview: + - security.openshift.io/v1 + PodTemplate: + - v1 + PriorityClass: + - scheduling.k8s.io/v1beta1 + Project: + - project.openshift.io/v1 + ProjectRequest: + - project.openshift.io/v1 + Prometheus: + - monitoring.coreos.com/v1 + PrometheusRule: + - monitoring.coreos.com/v1 + RangeAllocation: + - security.openshift.io/v1 + ReplicaSet: + - apps/v1 + - apps/v1beta2 + - extensions/v1beta1 + ReplicationController: + - v1 + ReplicationControllerDummy: + - extensions/v1beta1 + ResourceAccessReview: + - authorization.openshift.io/v1 + ResourceQuota: + - v1 + Role: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBinding: + - authorization.openshift.io/v1 + - rbac.authorization.k8s.io/v1 + - rbac.authorization.k8s.io/v1beta1 + RoleBindingRestriction: + - authorization.openshift.io/v1 + Route: + - route.openshift.io/v1 + Scale: + - apps.openshift.io/v1 + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - extensions/v1beta1 + - v1 + Secret: + - v1 + SecretList: + - image.openshift.io/v1 + SecurityContextConstraints: + - security.openshift.io/v1 + - v1 + SelfSubjectAccessReview: + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SelfSubjectRulesReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + Service: + - v1 + ServiceAccount: + - v1 + ServiceBinding: + - servicecatalog.k8s.io/v1beta1 + ServiceBroker: + - servicecatalog.k8s.io/v1beta1 + ServiceClass: + - servicecatalog.k8s.io/v1beta1 + ServiceInstance: + - servicecatalog.k8s.io/v1beta1 + ServiceMonitor: + - monitoring.coreos.com/v1 + ServicePlan: + - servicecatalog.k8s.io/v1beta1 + StatefulSet: + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + StorageClass: + - storage.k8s.io/v1 + - storage.k8s.io/v1beta1 + SubjectAccessReview: + - authorization.openshift.io/v1 + - authorization.k8s.io/v1 + - authorization.k8s.io/v1beta1 + SubjectRulesReview: + - authorization.openshift.io/v1 + Template: + - template.openshift.io/v1 + TemplateInstance: + - template.openshift.io/v1 + TokenReview: + - authentication.k8s.io/v1 + - authentication.k8s.io/v1beta1 + User: + - user.openshift.io/v1 + UserIdentityMapping: + - user.openshift.io/v1 + ValidatingWebhookConfiguration: + - admissionregistration.k8s.io/v1beta1 + VolumeAttachment: + - storage.k8s.io/v1beta1 diff --git a/internal/metadata/k8sfiles.go b/internal/metadata/k8sfiles.go new file mode 100644 index 000000000..dae390a5f --- /dev/null +++ b/internal/metadata/k8sfiles.go @@ -0,0 +1,80 @@ +/* +Copyright IBM Corporation 2020 + +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 metadata + +import ( + "io/ioutil" + + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime/serializer" + + "github.com/konveyor/move2kube/internal/apiresourceset" + common "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +//K8sFilesLoader Implements Loader interface +type K8sFilesLoader struct { +} + +// UpdatePlan - output a plan based on the input directory contents +func (i K8sFilesLoader) UpdatePlan(inputPath string, plan *plantypes.Plan) error { + codecs := serializer.NewCodecFactory((&apiresourceset.K8sAPIResourceSet{}).GetScheme()) + + files, err := common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize k8 yamls : %s", err) + } + for _, path := range files { + relpath, _ := plan.GetRelativePath(path) + data, err := ioutil.ReadFile(path) + if err != nil { + log.Debugf("ignoring file %s", path) + continue + } + _, _, err = codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", path) + continue + } else { + plan.Spec.Inputs.K8sFiles = append(plan.Spec.Inputs.K8sFiles, relpath) + } + } + return nil +} + +// LoadToIR loads k8s files as cached objects +func (i K8sFilesLoader) LoadToIR(p plantypes.Plan, ir *irtypes.IR) error { + codecs := serializer.NewCodecFactory((&apiresourceset.K8sAPIResourceSet{}).GetScheme()) + for _, path := range p.Spec.Inputs.K8sFiles { + fullpath := p.GetFullPath(path) + data, err := ioutil.ReadFile(fullpath) + if err != nil { + log.Debugf("ignoring file %s", path) + continue + } + obj, _, err := codecs.UniversalDeserializer().Decode(data, nil, nil) + if err != nil { + log.Debugf("ignoring file %s since serialization failed", path) + continue + } else { + ir.CachedObjects = append(ir.CachedObjects, obj) + } + } + return nil +} diff --git a/internal/metadata/metadata.go b/internal/metadata/metadata.go new file mode 100644 index 000000000..acf6b8b83 --- /dev/null +++ b/internal/metadata/metadata.go @@ -0,0 +1,34 @@ +/* +Copyright IBM Corporation 2020 + +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 metadata + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// Loader handles loading of various metadata +type Loader interface { + UpdatePlan(inputPath string, p *plantypes.Plan) error + LoadToIR(p plantypes.Plan, ir *irtypes.IR) error +} + +// GetLoaders returns planner for given format +func GetLoaders() []Loader { + var planners = []Loader{new(ClusterMDLoader), new(K8sFilesLoader), new(QACacheLoader)} + return planners +} diff --git a/internal/metadata/qacaches.go b/internal/metadata/qacaches.go new file mode 100644 index 000000000..54bf375c7 --- /dev/null +++ b/internal/metadata/qacaches.go @@ -0,0 +1,57 @@ +/* +Copyright IBM Corporation 2020 + +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 metadata + +import ( + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/qaengine" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +// QACacheLoader loads the qa caches +type QACacheLoader struct { +} + +// UpdatePlan - output a plan based on the input directory contents +func (i QACacheLoader) UpdatePlan(inputPath string, plan *plantypes.Plan) error { + files, err := common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize qacache metadata yamls : %s", err) + } + for _, path := range files { + cm := new(qatypes.Cache) + if common.ReadYaml(path, &cm) == nil && cm.Kind == string(qatypes.QACacheKind) { + relpath, _ := plan.GetRelativePath(path) + plan.Spec.Inputs.QACaches = append(plan.Spec.Inputs.QACaches, relpath) + } + } + return nil +} + +// LoadToIR starts the cache responders +func (i QACacheLoader) LoadToIR(p plantypes.Plan, ir *irtypes.IR) error { + cachepaths := []string{} + for i := len(p.Spec.Inputs.QACaches) - 1; i >= 0; i-- { + cachepaths = append(cachepaths, p.GetFullPath(p.Spec.Inputs.QACaches[i])) + } + qaengine.AddCaches(cachepaths) + return nil +} diff --git a/internal/metadata/testdata/emptyfiles/test1.yaml b/internal/metadata/testdata/emptyfiles/test1.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/internal/metadata/testdata/emptyfiles/test2.yml b/internal/metadata/testdata/emptyfiles/test2.yml new file mode 100644 index 000000000..e69de29bb diff --git a/internal/metadata/testdata/invalidfiles/test1.yaml b/internal/metadata/testdata/invalidfiles/test1.yaml new file mode 100644 index 000000000..a2eebca9c --- /dev/null +++ b/internal/metadata/testdata/invalidfiles/test1.yaml @@ -0,0 +1,10 @@ +--- +american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees +national: + - New York Mets + - Chicago Cubs + - Atlanta Braves +... \ No newline at end of file diff --git a/internal/metadata/testdata/invalidfiles/test2.yml b/internal/metadata/testdata/invalidfiles/test2.yml new file mode 100644 index 000000000..5c9bba174 --- /dev/null +++ b/internal/metadata/testdata/invalidfiles/test2.yml @@ -0,0 +1,10 @@ +--- +- + name: Mark McGwire + hr: 65 + avg: 0.278 +- + name: Sammy Sosa + hr: 63 + avg: 0.288 +... \ No newline at end of file diff --git a/internal/metadata/testdata/validfiles/test1.yaml b/internal/metadata/testdata/validfiles/test1.yaml new file mode 100644 index 000000000..99eaa5077 --- /dev/null +++ b/internal/metadata/testdata/validfiles/test1.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: name1 +spec: + storageClasses: + - class1 + - class2 + apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/metadata/testdata/validfiles/test2.yml b/internal/metadata/testdata/validfiles/test2.yml new file mode 100644 index 000000000..99eaa5077 --- /dev/null +++ b/internal/metadata/testdata/validfiles/test2.yml @@ -0,0 +1,19 @@ +--- +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: name1 +spec: + storageClasses: + - class1 + - class2 + apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/metadata/testdata/validfilesnostorageclasses/test1.yaml b/internal/metadata/testdata/validfilesnostorageclasses/test1.yaml new file mode 100644 index 000000000..d58236fb6 --- /dev/null +++ b/internal/metadata/testdata/validfilesnostorageclasses/test1.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: name1 +spec: + apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/metadata/testdata/validfilesnostorageclasses/test2.yml b/internal/metadata/testdata/validfilesnostorageclasses/test2.yml new file mode 100644 index 000000000..2d323e0a1 --- /dev/null +++ b/internal/metadata/testdata/validfilesnostorageclasses/test2.yml @@ -0,0 +1,16 @@ +--- +apiVersion: move2kube.io/v1alpha1 +kind: ClusterMetadata +metadata: + name: name1 +spec: + apiKindVersionMap: + key1: + - 1.0.0 + - 1.1.0 + - 1.1.1 + key2: + - 2.0.0 + - 2.2.0 + - 2.2.2 +... diff --git a/internal/move2kube/collector.go b/internal/move2kube/collector.go new file mode 100644 index 000000000..85fcda564 --- /dev/null +++ b/internal/move2kube/collector.go @@ -0,0 +1,68 @@ +/* +Copyright IBM Corporation 2020 + +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 move2kube + +import ( + "os" + "strings" + + log "github.com/sirupsen/logrus" + + collector "github.com/konveyor/move2kube/internal/collector" + common "github.com/konveyor/move2kube/internal/common" +) + +//Collect gets the metadata from multiple sources, filters it and dumps it into files within source directory +func Collect(inputPath string, outputPath string, annotations []string) { + var collectors, err = collector.GetCollectors() + if err != nil { + log.Fatal(err) + } + //Creating the output directory if it does not exist + err = os.MkdirAll(outputPath, common.DefaultDirectoryPermission) + if err != nil { + log.Fatalf("Unable to create output directory %s : %s", outputPath, err) + } + log.Infoln("Begin collection") + for _, l := range collectors { + if len(annotations) != 0 { + collectorannotations := l.GetAnnotations() + if !hasOverlap(annotations, collectorannotations) { + continue + } + } + log.Infof("[%T] Begin collection", l) + err = l.Collect(inputPath, outputPath) + if err != nil { + log.Warnf("[%T] Failed : %s", l, err.Error()) + } else { + log.Infof("[%T] Done", l) + } + } + log.Infoln("Collection done") +} + +func hasOverlap(a []string, b []string) bool { + for _, val1 := range a { + for _, val2 := range b { + if strings.EqualFold(val1, val2) { + return true + } + } + } + return false +} diff --git a/internal/move2kube/planner.go b/internal/move2kube/planner.go new file mode 100644 index 000000000..aa35d4f71 --- /dev/null +++ b/internal/move2kube/planner.go @@ -0,0 +1,224 @@ +/* +Copyright IBM Corporation 2020 + +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 move2kube + +import ( + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/metadata" + "github.com/konveyor/move2kube/internal/qaengine" + "github.com/konveyor/move2kube/internal/source" + plantypes "github.com/konveyor/move2kube/types/plan" + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +//CreatePlan creates the plan from all planners +func CreatePlan(inputPath string, prjName string) plantypes.Plan { + var p = plantypes.NewPlan() + p.Name = prjName + // Setup rootdir. + p.Spec.Inputs.RootDir = inputPath + + translationPlanners := source.GetSourceLoaders() + log.Infoln("Planning Translation") + for _, l := range translationPlanners { + log.Infof("[%T] Planning translation", l) + services, err := l.GetServiceOptions(inputPath, p) + if err != nil { + log.Warnf("[%T] Failed : %s", l, err) + } else { + p.AddServicesToPlan(services) + log.Infof("[%T] Done", l) + } + } + log.Infoln("Translation planning done") + + log.Infoln("Planning Metadata") + metadataPlanners := metadata.GetLoaders() + for _, l := range metadataPlanners { + log.Infof("[%T] Planning metadata", l) + err := l.UpdatePlan(inputPath, &p) + if err != nil { + log.Warnf("[%T] Failed : %s", l, err) + } else { + log.Infof("[%T] Done", l) + } + } + log.Infoln("Metadata planning done") + return p +} + +// CuratePlan allows curation the plan with the qa engine +func CuratePlan(p plantypes.Plan) plantypes.Plan { + // Load qa caches + cachepaths := []string{} + for i := len(p.Spec.Inputs.QACaches) - 1; i >= 0; i-- { + cachepaths = append(cachepaths, p.GetFullPath(p.Spec.Inputs.QACaches[i])) + } + qaengine.AddCaches(cachepaths) + // Identify services of interest + servicenames := []string{} + for sn := range p.Spec.Inputs.Services { + servicenames = append(servicenames, sn) + } + problem, err := qatypes.NewMultiSelectProblem("Select all services that are needed:", []string{"The services unselected here will be ignored."}, servicenames, servicenames) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + selectedServices, err := problem.GetSliceAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + planservices := make(map[string][]plantypes.Service) + for _, s := range selectedServices { + planservices[s] = p.Spec.Inputs.Services[s] + } + p.Spec.Inputs.Services = planservices + + // Identify containerization techniques of interest + conTypes := []string{} + for _, s := range p.Spec.Inputs.Services { + for _, so := range s { + if !common.IsStringPresent(conTypes, string(so.ContainerBuildType)) { + conTypes = append(conTypes, string(so.ContainerBuildType)) + } + } + } + problem, err = qatypes.NewMultiSelectProblem("Select all containerization modes that is of interest:", []string{"The services which does not support any of the containerization technique you are interested will be ignored."}, conTypes, conTypes) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + selectedConTypes, err := problem.GetSliceAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + if len(selectedConTypes) == 0 { + log.Fatalf("No containerization technique was selected; Terminating.") + } + services := make(map[string][]plantypes.Service) + for sn, s := range p.Spec.Inputs.Services { + sConTypes := []string{} + for _, so := range s { + if common.IsStringPresent(selectedConTypes, string(so.ContainerBuildType)) { + sConTypes = append(sConTypes, string(so.ContainerBuildType)) + } + } + if len(sConTypes) == 0 { + log.Warnf("Ignoring service %s, since it does not support any selected containerization technique.", sn) + continue + } + selectedSConType := sConTypes[0] + if len(sConTypes) > 1 { + problem, err := qatypes.NewSelectProblem("Select containerization technique for service "+sn+":", []string{"Choose the containerization technique of interest."}, selectedSConType, sConTypes) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + selectedSConType, err = problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + } + for _, so := range s { + if selectedSConType == string(so.ContainerBuildType) { + if len(so.ContainerizationTargetOptions) > 1 { + problem, err := qatypes.NewSelectProblem("Select containerization technique's mode for service "+sn+":", []string{"Choose the containerization technique mode of interest."}, so.ContainerizationTargetOptions[0], so.ContainerizationTargetOptions) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + selectedSConMode, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + so.ContainerizationTargetOptions = []string{selectedSConMode} + } + services[sn] = []plantypes.Service{so} + break + } + } + } + p.Spec.Inputs.Services = services + + // Choose output artifact type + artifactTypeList := make([]string, 3) + artifactTypeList[0] = string(plantypes.Yamls) + artifactTypeList[1] = string(plantypes.Helm) + artifactTypeList[2] = string(plantypes.Knative) + problem, err = qatypes.NewSelectProblem("Choose the artifact type:", []string{"Yamls - Generate Kubernetes Yamls", "Helm - Generate Helm chart", "Knative - Create Knative artifacts"}, string(plantypes.Yamls), artifactTypeList) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + artifactType, err := problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + p.Spec.Outputs.Kubernetes.ArtifactType = plantypes.TargetArtifactTypeValue(artifactType) + + // Choose cluster type to target + clusters := metadata.ClusterMDLoader{}.GetClusters(p) + clusterTypeList := []string{} + for c := range clusters { + clusterTypeList = append(clusterTypeList, c) + } + problem, err = qatypes.NewSelectProblem("Choose the cluster type:", []string{"Choose the cluster type you would like to target"}, string(common.DefaultClusterType), clusterTypeList) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + p.Spec.Outputs.Kubernetes.ClusterType, err = problem.GetStringAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + + return p +} + +//WritePlan writes the plan +func WritePlan(plan plantypes.Plan, outputPath string) error { + err := common.WriteYaml(outputPath, plan) + return err +} + +//ReadPlan reads the plan +func ReadPlan(planFile string) (plantypes.Plan, error) { + var plan plantypes.Plan + err := common.ReadYaml(planFile, &plan) + return plan, err +} diff --git a/internal/move2kube/planner_test.go b/internal/move2kube/planner_test.go new file mode 100644 index 000000000..89f178bce --- /dev/null +++ b/internal/move2kube/planner_test.go @@ -0,0 +1,106 @@ +/* +Copyright IBM Corporation 2020 + +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 move2kube_test + +import ( + "os" + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/move2kube" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +func TestCreatePlan(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("create plan for empty app and without the cache folder", func(t *testing.T) { + // Setup + inputPath := t.TempDir() + prjName := "project1" + + want := plantypes.NewPlan() + want.Name = prjName + want.Spec.Inputs.RootDir = inputPath + + // Test + p := move2kube.CreatePlan(inputPath, prjName) + if !reflect.DeepEqual(p, want) { + t.Fatal("Failed to create the plan properly. Expected:", want, "Actual", p) + } + }) + + t.Run("create plan for empty app", func(t *testing.T) { + // Setup + inputPath := t.TempDir() + prjName := "project1" + + // If the cache folder exists delete it + if _, err := os.Stat(common.AssetsPath); !os.IsNotExist(err) { + if err := os.RemoveAll(common.AssetsPath); err != nil { + t.Fatal("Failed to remove the cache folder from previous runs. Error:", err) + } + } + // Create the cache folder (.m2k) it expects to find. + if err := os.MkdirAll(common.AssetsPath, os.ModeDir|os.ModePerm); err != nil { + t.Fatal("Failed to make the common.AssetsPath directory:", common.AssetsPath, "Error:", err) + } + defer os.RemoveAll(common.AssetsPath) + + want := plantypes.NewPlan() + want.Name = prjName + want.Spec.Inputs.RootDir = inputPath + + // Test + p := move2kube.CreatePlan(inputPath, prjName) + if !reflect.DeepEqual(p, want) { + t.Fatal("Failed to create the plan properly. Expected:", want, "Actual", p) + } + }) + + t.Run("create plan for a simple nodejs app", func(t *testing.T) { + // Setup + inputPath := "../../samples/nodejs" + prjName := "nodejs-app" + + // If the cache folder exists delete it + if _, err := os.Stat(common.AssetsPath); !os.IsNotExist(err) { + if err := os.RemoveAll(common.AssetsPath); err != nil { + t.Fatal("Failed to remove the cache folder from previous runs. Error:", err) + } + } + // Create the cache folder (.m2k) it expects to find. + if err := os.Mkdir(common.AssetsPath, os.ModeDir|os.ModePerm); err != nil { + t.Fatal("Failed to make the common.AssetsPath directory:", common.AssetsPath, "Error:", err) + } + defer os.RemoveAll(common.AssetsPath) + + want := plantypes.NewPlan() + if err := common.ReadYaml("testdata/expectedplanfornodejsapp.yaml", &want); err != nil { + t.Fatal("failed to read the expected output plan from yaml. Error:", err) + } + + // Test + p := move2kube.CreatePlan(inputPath, prjName) + if !reflect.DeepEqual(p, want) { + t.Fatal("Failed to create the plan properly. Expected:", want, "Actual", p) + } + }) +} diff --git a/internal/move2kube/testdata/expectedplanfornodejsapp.yaml b/internal/move2kube/testdata/expectedplanfornodejsapp.yaml new file mode 100644 index 000000000..c4aae3d00 --- /dev/null +++ b/internal/move2kube/testdata/expectedplanfornodejsapp.yaml @@ -0,0 +1,29 @@ +apiVersion: move2kube.io/v1alpha1 +kind: Plan +metadata: + name: nodejs-app +spec: + inputs: + rootDir: ../../samples/nodejs + services: + nodejs: + - serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: CNB + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true + outputs: + kubernetes: + artifactType: Yamls + clusterType: Kubernetes diff --git a/internal/move2kube/translator.go b/internal/move2kube/translator.go new file mode 100644 index 000000000..07d74c4cb --- /dev/null +++ b/internal/move2kube/translator.go @@ -0,0 +1,85 @@ +/* +Copyright IBM Corporation 2020 + +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 move2kube + +import ( + "os" + + customize "github.com/konveyor/move2kube/internal/customizer" + "github.com/konveyor/move2kube/internal/metadata" + optimize "github.com/konveyor/move2kube/internal/optimizer" + parameterize "github.com/konveyor/move2kube/internal/parameterizer" + "github.com/konveyor/move2kube/internal/source" + transform "github.com/konveyor/move2kube/internal/transformer" + plantypes "github.com/konveyor/move2kube/types/plan" + + log "github.com/sirupsen/logrus" +) + +// Translate translates the artifacts and writes output +func Translate(p plantypes.Plan, outpath string) { + sourceir, _ := source.Translate(p) + + log.Debugf("Total storages loaded : %d", len(sourceir.Storages)) + + log.Infoln("Begin Metadata loading") + metadataPlanners := metadata.GetLoaders() + for _, l := range metadataPlanners { + log.Debugf("[%T] Begin metadata loading", l) + err := l.LoadToIR(p, &sourceir) + if err != nil { + log.Warnf("[%T] Failed : %s", l, err.Error()) + } else { + log.Debugf("[%T] Done", l) + } + } + log.Infoln("Metadata loading done") + + log.Debugf("Total services loaded : %d", len(sourceir.Services)) + log.Debugf("Total containers loaded : %d", len(sourceir.Containers)) + optimizedir, _ := optimize.Optimize(sourceir) + + os.RemoveAll(outpath) + dct := transform.ComposeTransformer{} + err := dct.Transform(optimizedir) + if err != nil { + log.Errorf("Error during translate docker compose file : %s", err) + } + err = dct.WriteObjects(outpath) + if err != nil { + log.Errorf("Unable to write docker compose objects : %s", err) + } + + log.Debugf("Total services optimized : %d", len(optimizedir.Services)) + ir, _ := customize.Customize(optimizedir) + log.Debugf("Total storages customized : %d", len(optimizedir.Storages)) + if p.Spec.Outputs.Kubernetes.ArtifactType != plantypes.Yamls && p.Spec.Outputs.Kubernetes.ArtifactType != plantypes.Knative { + ir, _ = parameterize.Parameterize(ir) + } + + t := transform.GetTransformer(ir) + err = t.Transform(ir) + if err != nil { + log.Errorf("Error during translate : %s", err) + } + err = t.WriteObjects(outpath) + if err != nil { + log.Errorf("Unable to write objects : %s", err) + } + + log.Info("Execution completed") +} diff --git a/internal/move2kube/version.go b/internal/move2kube/version.go new file mode 100644 index 000000000..df0c76321 --- /dev/null +++ b/internal/move2kube/version.go @@ -0,0 +1,33 @@ +/* +Copyright IBM Corporation 2020 + +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 move2kube + +import ( + "gopkg.in/yaml.v3" + + "github.com/konveyor/move2kube/types/info" +) + +// GetVersion returns the version +func GetVersion(long bool) string { + if !long { + return info.GetVersion() + } + v := info.GetVersionInfo() + ver, _ := yaml.Marshal(v) + return string(ver) +} diff --git a/internal/optimizer/imagepullpolicyoptimizer.go b/internal/optimizer/imagepullpolicyoptimizer.go new file mode 100644 index 000000000..1a4737940 --- /dev/null +++ b/internal/optimizer/imagepullpolicyoptimizer.go @@ -0,0 +1,38 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" + v1 "k8s.io/api/core/v1" +) + +// imagePullPolicyOptimizer sets the pull policy to be always +type imagePullPolicyOptimizer struct { +} + +func (ep imagePullPolicyOptimizer) optimize(ir irtypes.IR) (irtypes.IR, error) { + for k, scObj := range ir.Services { + for ci, container := range scObj.Containers { + container.ImagePullPolicy = v1.PullAlways + scObj.Containers[ci] = container + } + ir.Services[k] = scObj + } + + return ir, nil +} diff --git a/internal/optimizer/ingressoptimizer.go b/internal/optimizer/ingressoptimizer.go new file mode 100644 index 000000000..d6ece4210 --- /dev/null +++ b/internal/optimizer/ingressoptimizer.go @@ -0,0 +1,72 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + "log" + + "github.com/konveyor/move2kube/internal/qaengine" + irtypes "github.com/konveyor/move2kube/internal/types" + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +// ingressOptimizer identifies the services that needs to be externally exposed +type ingressOptimizer struct { +} + +func (ingresso ingressOptimizer) optimize(ir irtypes.IR) (irtypes.IR, error) { + + if len(ir.Services) == 0 { + return ir, nil + } + + // Obtain a listing of services. + i := 0 + servicesList := make([]string, len(ir.Services)) + for k := range ir.Services { + servicesList[i] = k + i++ + } + + problem, err := qatypes.NewMultiSelectProblem("Which services should we expose?", []string{"An Ingress object will be created for every exposed service."}, servicesList, servicesList) + if err != nil { + log.Fatalf("Unable to create problem : %s", err) + } + problem, err = qaengine.FetchAnswer(problem) + if err != nil { + log.Fatalf("Unable to fetch answer : %s", err) + } + services, err := problem.GetSliceAnswer() + if err != nil { + log.Fatalf("Unable to get answer : %s", err) + } + + for _, k := range services { + tempService := ir.Services[k] + + // Set the line in annotations + if tempService.Annotations == nil { + tempService.Annotations = make(map[string]string) + } + tempService.Annotations["move2kube.service.expose"] = "true" + // Also set the special field + tempService.ExposeService = true + ir.Services[k] = tempService + } + + return ir, nil +} diff --git a/internal/optimizer/normalizecharactersoptimizer.go b/internal/optimizer/normalizecharactersoptimizer.go new file mode 100644 index 000000000..90449b47d --- /dev/null +++ b/internal/optimizer/normalizecharactersoptimizer.go @@ -0,0 +1,64 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + "regexp" + "strings" + + corev1 "k8s.io/api/core/v1" + + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// normalizeCharacterOptimizer identifies non-allowed characters and replaces them +type normalizeCharacterOptimizer struct { +} + +func (po normalizeCharacterOptimizer) optimize(ir irtypes.IR) (irtypes.IR, error) { + //TODO: Make this generic to ensure all fields have valid names + for k := range ir.Services { + scObj := ir.Services[k] + for _, serviceContainer := range scObj.Containers { + var tmpEnvArray []corev1.EnvVar + for _, env := range serviceContainer.Env { + if !strings.Contains(env.Name, "affinity") { + env.Name = strings.Trim(env.Name, "\t \n") + env.Value = strings.Trim(env.Value, "\t \n") + tmpString, err := stripQuotation(env.Name) + if err == nil { + env.Name = tmpString + } + tmpString, err = stripQuotation(env.Value) + if err == nil { + env.Value = tmpString + } + tmpEnvArray = append(tmpEnvArray, env) + } + } + serviceContainer.Env = tmpEnvArray + } + ir.Services[k] = scObj + } + return ir, nil +} + +func stripQuotation(inputString string) (string, error) { + regex := regexp.MustCompile(`^[',"](.*)[',"]$`) + + return regex.ReplaceAllString(inputString, `$1`), nil +} diff --git a/internal/optimizer/optimizer.go b/internal/optimizer/optimizer.go new file mode 100644 index 000000000..5981d6df4 --- /dev/null +++ b/internal/optimizer/optimizer.go @@ -0,0 +1,52 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + log "github.com/sirupsen/logrus" + + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// optimizer optimizes the configuration +type optimizer interface { + optimize(sourceir irtypes.IR) (irtypes.IR, error) +} + +// getOptimizers returns loader for given format +func getOptimizers() []optimizer { + var l = []optimizer{new(ingressOptimizer), new(normalizeCharacterOptimizer), new(replicaOptimizer), new(imagePullPolicyOptimizer), new(portMergeOptimizer)} + return l +} + +// Optimize optimizes the application artifacts +func Optimize(ir irtypes.IR) (irtypes.IR, error) { + optimizers := getOptimizers() + log.Infoln("Begin Optimization") + for _, o := range optimizers { + log.Debugf("[%T] Begin Optimization", o) + var err error + ir, err = o.optimize(ir) + if err != nil { + log.Warnf("[%T] Failed : %s", o, err.Error()) + } else { + log.Debugf("[%T] Done", o) + } + } + log.Infoln("Optimization done") + return ir, nil +} diff --git a/internal/optimizer/portmergeoptimizer.go b/internal/optimizer/portmergeoptimizer.go new file mode 100644 index 000000000..6309b842f --- /dev/null +++ b/internal/optimizer/portmergeoptimizer.go @@ -0,0 +1,44 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" + corev1 "k8s.io/api/core/v1" +) + +//PortMergeOptimizer implements Optimizer interface +type portMergeOptimizer struct { +} + +//Optimize uses data from ir containers to fill ir.services +func (ep portMergeOptimizer) optimize(ir irtypes.IR) (irtypes.IR, error) { + for k, scObj := range ir.Services { + for csIndex, containerSection := range scObj.Containers { + if c, ok := ir.GetContainer(containerSection.Image); ok { + for _, exposedPort := range c.ExposedPorts { + containerSection.Ports = append(containerSection.Ports, corev1.ContainerPort{ContainerPort: int32(exposedPort)}) + } + scObj.Containers[csIndex] = containerSection + } + } + + ir.Services[k] = scObj + } + + return ir, nil +} diff --git a/internal/optimizer/replicaoptimizer.go b/internal/optimizer/replicaoptimizer.go new file mode 100644 index 000000000..47614e52a --- /dev/null +++ b/internal/optimizer/replicaoptimizer.go @@ -0,0 +1,40 @@ +/* +Copyright IBM Corporation 2020 + +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 optimize + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// replicaOptimizer sets the minimum number of replicas +type replicaOptimizer struct { +} + +const ( + minReplicas = 2 +) + +func (ep replicaOptimizer) optimize(ir irtypes.IR) (irtypes.IR, error) { + for k, scObj := range ir.Services { + if scObj.Replicas < minReplicas { + scObj.Replicas = minReplicas + } + ir.Services[k] = scObj + } + + return ir, nil +} diff --git a/internal/parameterizer/imagenameparameterizer.go b/internal/parameterizer/imagenameparameterizer.go new file mode 100644 index 000000000..e458d2741 --- /dev/null +++ b/internal/parameterizer/imagenameparameterizer.go @@ -0,0 +1,69 @@ +/* +Copyright IBM Corporation 2020 + +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 parameterize + +import ( + "strings" + + "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" + outputtypes "github.com/konveyor/move2kube/types/output" +) + +// imageNameParameterizer parameterizes the image names +type imageNameParameterizer struct { +} + +func (it imageNameParameterizer) parameterize(ir *irtypes.IR) error { + newimages := []string{} + for _, container := range ir.Containers { + if container.New { + for _, img := range container.ImageNames { + newimages = append(newimages, ir.Kubernetes.RegistryURL+"/"+ir.Kubernetes.RegistryNamespace+"/"+img) + } + } + } + + ir.Values.Services = make(map[string]outputtypes.Service) + for _, service := range ir.Services { + ir.Values.Services[service.Name] = outputtypes.Service{ + Containers: make(map[string]outputtypes.Container), + } + for ci, serviceContainer := range service.Containers { + nImageName := "" + parts := strings.Split(serviceContainer.Image, "/") + if len(parts) == 3 { + nImageName += parts[0] + "/" + } + if len(parts) > 1 { + nImageName += parts[1] + "/" + } + if common.IsStringPresent(newimages, serviceContainer.Image) { + nImageName = outputtypes.ParameterRegistryPrefix + } + imageName := parts[len(parts)-1] + im, tag := common.GetImageNameAndTag(imageName) + ir.Values.Services[service.Name].Containers[serviceContainer.Name] = outputtypes.Container{TagName: tag} + newTag := "{{ index .Values." + outputtypes.ServicesTag + " \"" + service.Name + "\" \"" + outputtypes.ContainersTag + "\" \"" + serviceContainer.Name + "\" \"" + outputtypes.ImageTagTag + "\" }}" + nImageName += im + ":" + newTag + serviceContainer.Image = nImageName + service.Containers[ci] = serviceContainer + } + ir.Services[service.Name] = service + } + return nil +} diff --git a/internal/parameterizer/parameterizer.go b/internal/parameterizer/parameterizer.go new file mode 100644 index 000000000..fba627551 --- /dev/null +++ b/internal/parameterizer/parameterizer.go @@ -0,0 +1,51 @@ +/* +Copyright IBM Corporation 2020 + +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 parameterize + +import ( + log "github.com/sirupsen/logrus" + + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// Parameterizer paramertizers the configuration for helm +type Parameterizer interface { + parameterize(ir *irtypes.IR) error +} + +// getParameterizers returns different supported paramterizers +func getParameterizers() []Parameterizer { + return []Parameterizer{new(imageNameParameterizer), new(storageClassParameterizer)} +} + +// Parameterize parameterizes for usage as a helm chart +func Parameterize(ir irtypes.IR) (irtypes.IR, error) { + var parameterizers = getParameterizers() + log.Infoln("Begin Parameterization") + for _, p := range parameterizers { + log.Debugf("[%T] Begin Parameterization", p) + //TODO: Handle conflicting service names and invalid characters in objects + err := p.parameterize(&ir) + if err != nil { + log.Warnf("[%T] Failed : %s", p, err.Error()) + } else { + log.Debugf("[%T] Done", p) + } + } + log.Infoln("Parameterization done") + return ir, nil +} diff --git a/internal/parameterizer/storageclassparameterizer.go b/internal/parameterizer/storageclassparameterizer.go new file mode 100644 index 000000000..221ab7b6c --- /dev/null +++ b/internal/parameterizer/storageclassparameterizer.go @@ -0,0 +1,59 @@ +/* +Copyright IBM Corporation 2020 + +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 parameterize + +import ( + irtypes "github.com/konveyor/move2kube/internal/types" + log "github.com/sirupsen/logrus" +) + +// StorageClassParameterizer parameterizes the storage class +type storageClassParameterizer struct { +} + +// Parameterize parameterizes the storage class +func (sc storageClassParameterizer) parameterize(ir *irtypes.IR) error { + scMap := make(map[string][]int) + + for i, storage := range ir.Storages { + if storage.StorageType == irtypes.PVCKind { + scName := *storage.PersistentVolumeClaimSpec.StorageClassName + if indices, ok := scMap[scName]; ok { + indices = append(indices, i) + scMap[scName] = indices + } else { + scMap[scName] = []int{i} + } + } + } + + if len(scMap) > 1 { + log.Warnf("Storage class not common across all PVC. Hence, parameterization is skipped.") + return nil + } + + paramSC := "{{ .Values.storageclass }}" + + for scName, indexList := range scMap { + ir.Values.StorageClass = scName + for _, i := range indexList { + ir.Storages[i].PersistentVolumeClaimSpec.StorageClassName = ¶mSC + } + } + + return nil +} diff --git a/internal/qaengine/cacheengine.go b/internal/qaengine/cacheengine.go new file mode 100644 index 000000000..29e98bbcf --- /dev/null +++ b/internal/qaengine/cacheengine.go @@ -0,0 +1,43 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +// CacheEngine handles cache +type CacheEngine struct { + cache qatypes.Cache +} + +// NewCacheEngine creates a new cache instance +func NewCacheEngine(cf string) Engine { + ce := new(CacheEngine) + ce.cache = qatypes.NewCache(cf) + return ce +} + +// StartEngine starts the cache engine +func (c *CacheEngine) StartEngine() error { + return c.cache.Load() +} + +// FetchAnswer fetches the answer using cache +func (c *CacheEngine) FetchAnswer(prob qatypes.Problem) (ans qatypes.Problem, err error) { + return c.cache.GetSolution(prob) +} diff --git a/internal/qaengine/cliengine.go b/internal/qaengine/cliengine.go new file mode 100644 index 000000000..cc09405be --- /dev/null +++ b/internal/qaengine/cliengine.go @@ -0,0 +1,177 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + "fmt" + "strconv" + + "github.com/AlecAivazis/survey/v2" + log "github.com/sirupsen/logrus" + + qatypes "github.com/konveyor/move2kube/types/qaengine" +) + +// CliEngine handles the CLI based qa +type CliEngine struct { +} + +// NewCliEngine creates a new instance of cli engine +func NewCliEngine() Engine { + ce := new(CliEngine) + return ce +} + +// StartEngine starts the cli engine +func (c *CliEngine) StartEngine() error { + return nil +} + +// FetchAnswer fetches the answer using cli +func (c *CliEngine) FetchAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + switch prob.Solution.Type { + case qatypes.SelectSolutionFormType: + return c.fetchSelectAnswer(prob) + case qatypes.MultiSelectSolutionFormType: + return c.fetchMultiSelectAnswer(prob) + case qatypes.ConfirmSolutionFormType: + return c.fetchConfirmAnswer(prob) + case qatypes.InputSolutionFormType: + return c.fetchInputAnswer(prob) + case qatypes.MultilineSolutionFormType: + return c.fetchMultilineAnswer(prob) + case qatypes.PasswordSolutionFormType: + return c.fetchPasswordAnswer(prob) + default: + log.Fatalf("Unknown type found: %s", prob.Solution.Type) + } + return prob, fmt.Errorf("Unknown type found : %s", prob.Solution.Type) +} + +func (c *CliEngine) fetchSelectAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + var ans string = "" + d := "" + if len(prob.Solution.Default) > 0 { + d = prob.Solution.Default[0] + } else { + d = prob.Solution.Options[0] + } + prompt := &survey.Select{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + Options: prob.Solution.Options, + Default: d, + } + err = survey.AskOne(prompt, &ans) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer([]string{ans}) + return prob, err +} + +func (c *CliEngine) fetchMultiSelectAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + ans := []string{} + prompt := &survey.MultiSelect{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + Options: prob.Solution.Options, + Default: prob.Solution.Default, + } + err = survey.AskOne(prompt, &ans, survey.WithIcons(func(icons *survey.IconSet) { + icons.MarkedOption.Text = "[\u2713]" + })) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer(ans) + return prob, err +} + +func (c *CliEngine) fetchConfirmAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + ans := []string{} + var d bool + if len(prob.Solution.Default) > 0 { + d, err = strconv.ParseBool(prob.Solution.Default[0]) + if err != nil { + log.Warnf("Unable to parse default value : %s", err) + } + } + prompt := &survey.Confirm{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + Default: d, + } + err = survey.AskOne(prompt, &ans) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer(ans) + return prob, err +} + +func (c *CliEngine) fetchInputAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + var ans string + d := "" + if len(prob.Solution.Default) > 0 { + d = prob.Solution.Default[0] + } + prompt := &survey.Input{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + Default: d, + } + err = survey.AskOne(prompt, &ans) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer([]string{ans}) + return prob, err +} + +func (c *CliEngine) fetchMultilineAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + var ans string + d := "" + if len(prob.Solution.Default) > 0 { + d = prob.Solution.Default[0] + } + prompt := &survey.Multiline{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + Default: d, + } + err = survey.AskOne(prompt, &ans) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer([]string{ans}) + return prob, err +} + +func (c *CliEngine) fetchPasswordAnswer(prob qatypes.Problem) (answer qatypes.Problem, err error) { + var ans string + prompt := &survey.Password{ + Message: fmt.Sprintf("%d. %s \nHints: \n %s\n", prob.ID, prob.Desc, prob.Context), + } + err = survey.AskOne(prompt, &ans) + if err != nil { + log.Fatalf("Error while asking a question : %s", err) + return prob, err + } + err = prob.SetAnswer([]string{ans}) + return prob, err +} diff --git a/internal/qaengine/defaultengine.go b/internal/qaengine/defaultengine.go new file mode 100644 index 000000000..f34043bbd --- /dev/null +++ b/internal/qaengine/defaultengine.go @@ -0,0 +1,40 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import qatypes "github.com/konveyor/move2kube/types/qaengine" + +// DefaultEngine returns default values for all questions +type DefaultEngine struct { +} + +// NewDefaultEngine creates a new instance of default engine +func NewDefaultEngine() Engine { + ce := new(DefaultEngine) + return ce +} + +// StartEngine starts the default qa engine +func (c *DefaultEngine) StartEngine() error { + return nil +} + +// FetchAnswer fetches the default answers +func (c *DefaultEngine) FetchAnswer(prob qatypes.Problem) (ans qatypes.Problem, err error) { + err = prob.SetAnswer(prob.Solution.Default) + return prob, err +} diff --git a/internal/qaengine/engine.go b/internal/qaengine/engine.go new file mode 100644 index 000000000..beb3d2b3b --- /dev/null +++ b/internal/qaengine/engine.go @@ -0,0 +1,123 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + "os" + "path/filepath" + + "github.com/konveyor/move2kube/internal/common" + qatypes "github.com/konveyor/move2kube/types/qaengine" + log "github.com/sirupsen/logrus" +) + +// Engine defines interface for qa engines +type Engine interface { + StartEngine() error + FetchAnswer(prob qatypes.Problem) (ans qatypes.Problem, err error) +} + +var ( + engines []Engine + writeCache qatypes.Cache +) + +// StartEngine starts the QA Engines +func StartEngine(qaskip bool, qaport int, qadisablecli bool) { + if qaskip { + e := NewDefaultEngine() + err := e.StartEngine() + if err != nil { + log.Errorf("Ignoring engine %T due to error : %s", e, err) + } else { + engines = append(engines, e) + } + } else if !qadisablecli { + e := NewCliEngine() + err := e.StartEngine() + if err != nil { + log.Errorf("Ignoring engine %T due to error : %s", e, err) + } else { + engines = append(engines, e) + } + } else { + e := NewHTTPRESTEngine(qaport) + err := e.StartEngine() + if err != nil { + log.Errorf("Ignoring engine %T due to error : %s", e, err) + } else { + engines = append(engines, e) + } + } +} + +// AddCaches adds cache responders +func AddCaches(cacheFiles []string) { + cengines := []Engine{} + for _, cacheFile := range cacheFiles { + e := NewCacheEngine(cacheFile) + err := e.StartEngine() + if err != nil { + log.Errorf("Ignoring engine %T due to error : %s", e, err) + } else { + cengines = append(cengines, e) + } + } + engines = append(cengines, engines...) +} + +// FetchAnswer fetches the answer for the question +func FetchAnswer(prob qatypes.Problem) (ans qatypes.Problem, err error) { + for _, e := range engines { + ans, err = e.FetchAnswer(prob) + if err != nil { + log.Warnf("Error while fetching answer using engine %s : %s", e, err) + } else if ans.Resolved { + break + } + } + if !ans.Resolved { + for { + ans, err = engines[len(engines)-1].FetchAnswer(prob) + if err != nil { + log.Fatalf("Unable to get answer to %s : %s", ans.Desc, err) + } + if ans.Resolved { + break + } + } + } + if err == nil && ans.Resolved { + writeCache.AddProblemSolutionToCache(ans) + } else if err != nil { + log.Errorf("Unable to fetch answer : %s", err) + } + return ans, err +} + +// SetWriteCache sets the write cache +func SetWriteCache(cacheFile string) error { + dirpath := filepath.Dir(cacheFile) + if err := os.MkdirAll(dirpath, common.DefaultDirectoryPermission); err != nil { + // Create the qacache directory if it is missing + log.Errorf("Failed to create the qacache directory at path %q. Error: %q", dirpath, err) + return err + } + + writeCache = qatypes.NewCache(cacheFile) + return writeCache.Write() +} diff --git a/internal/qaengine/httprestengine.go b/internal/qaengine/httprestengine.go new file mode 100644 index 000000000..6c581994d --- /dev/null +++ b/internal/qaengine/httprestengine.go @@ -0,0 +1,143 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "strconv" + + "github.com/gorilla/mux" + qatypes "github.com/konveyor/move2kube/types/qaengine" + "github.com/phayes/freeport" + log "github.com/sirupsen/logrus" +) + +const ( + problemsURLPrefix = "/problems" + currentProblemURLPrefix = problemsURLPrefix + "/current" + currentSolutionURLPrefix = currentProblemURLPrefix + "/solution" +) + +// HTTPRESTEngine handles qa using HTTP REST services +type HTTPRESTEngine struct { + port int + currentProblem qatypes.Problem + problemChan chan qatypes.Problem + answerChan chan qatypes.Problem +} + +// NewHTTPRESTEngine creates a new instance of Http REST engine +func NewHTTPRESTEngine(qaport int) Engine { + e := new(HTTPRESTEngine) + e.port = qaport + e.currentProblem = qatypes.Problem{ID: 0, Resolved: true} + e.problemChan = make(chan qatypes.Problem) + e.answerChan = make(chan qatypes.Problem) + return e +} + +// StartEngine starts the QA Engine +func (h *HTTPRESTEngine) StartEngine() error { + if h.port == 0 { + var err error + h.port, err = freeport.GetFreePort() + if err != nil { + return fmt.Errorf("Unable to find a free port : %s", err) + } + } + // Create the REST router. + r := mux.NewRouter() + r.HandleFunc(currentProblemURLPrefix, h.problemHandler).Methods("GET") + r.HandleFunc(currentSolutionURLPrefix, h.solutionHandler).Methods("POST") + + http.Handle("/", r) + qaportstr := strconv.Itoa(h.port) + + listener, err := net.Listen("tcp", ":"+qaportstr) + if err != nil { + return fmt.Errorf("Unable to listen on port %d : %s", h.port, err) + } + go func(listener net.Listener) { + err := http.Serve(listener, nil) + if err != nil { + log.Fatalf("Unable to start qa server : %s", err) + } + }(listener) + log.Info("Started QA engine on: localhost:" + qaportstr) + return nil +} + +// FetchAnswer fetches the answer using a REST service +func (h *HTTPRESTEngine) FetchAnswer(prob qatypes.Problem) (ans qatypes.Problem, err error) { + if prob.ID == 0 { + prob.Resolved = true + } + if !prob.Resolved { + log.Debugf("Passing problem to HTTP REST QA Engine ID: %d, desc: %s", prob.ID, prob.Desc) + h.problemChan <- prob + prob = <-h.answerChan + if !prob.Resolved { + return prob, fmt.Errorf("Unable to resolve question %s", prob.Desc) + } + } + return prob, nil +} + +// problemHandler returns the current problem being handled +func (h *HTTPRESTEngine) problemHandler(w http.ResponseWriter, r *http.Request) { + log.Debug("Looking for a problem fron HTTP REST service") + // if currently problem is resolved + if h.currentProblem.Resolved || h.currentProblem.ID == 0 { + // Pick the next problem off the channel + h.currentProblem = <-h.problemChan + } + log.Debugf("QA Engine serves problem id: %d, desc: %s", h.currentProblem.ID, h.currentProblem.Desc) + // Send the problem to the request. + _ = json.NewEncoder(w).Encode(h.currentProblem) +} + +// solutionHandler accepts solution for a single open problem. +func (h *HTTPRESTEngine) solutionHandler(w http.ResponseWriter, r *http.Request) { + log.Debugf("QA Engine reading solution: %s", r.Body) + // Read out the solution + body, err := ioutil.ReadAll(r.Body) + if err != nil { + errstr := fmt.Sprintf("Error in reading posted solution: %s", err) + http.Error(w, "errstr", http.StatusInternalServerError) + log.Errorf(errstr) + } + var sol []string + err = json.Unmarshal(body, &sol) + if err != nil { + errstr := fmt.Sprintf("Error in un-marshalling solution in QA engine: %s", err) + http.Error(w, errstr, http.StatusInternalServerError) + log.Errorf(errstr) + } + log.Debugf("QA Engine receives solution: %s", sol) + err = h.currentProblem.SetAnswer(sol) + if err != nil { + errstr := fmt.Sprintf("Unsuitable answer : %s", err) + http.Error(w, errstr, http.StatusInternalServerError) + log.Errorf(errstr) + } else { + h.answerChan <- h.currentProblem + } +} diff --git a/internal/source/any2kube.go b/internal/source/any2kube.go new file mode 100644 index 000000000..c3cd5865f --- /dev/null +++ b/internal/source/any2kube.go @@ -0,0 +1,177 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + "bufio" + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// Any2KubeTranslator implements Translator interface for loading any source folder that can be containerized +type Any2KubeTranslator struct { +} + +// GetTranslatorType returns translator type +func (c Any2KubeTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.Any2KubeTranslation +} + +// GetServiceOptions - output a plan based on the input directory contents +func (c Any2KubeTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + services := make([]plantypes.Service, 0) + containerizers := new(containerizer.Containerizers) + containerizers.InitContainerizers(inputPath) + preContainerizedSourcePaths := []string{} + for _, existingservices := range plan.Spec.Inputs.Services { + for _, existingservice := range existingservices { + if len(existingservice.SourceArtifacts[plantypes.SourceDirectoryArtifactType]) > 0 { + preContainerizedSourcePaths = append(preContainerizedSourcePaths, existingservice.SourceArtifacts[plantypes.SourceDirectoryArtifactType][0]) + } + } + } + ignoreDirectories, ignoreContents := c.getIgnorePaths(inputPath) + err := filepath.Walk(inputPath, func(fullpath string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %s due to error: %s", fullpath, err) + return nil + } + if info.IsDir() { + path, _ := plan.GetRelativePath(fullpath) + if common.IsStringPresent(preContainerizedSourcePaths, path) { + return filepath.SkipDir //TODO: Should we go inside the directory in this case? + } + fullcleanpath, err := filepath.Abs(fullpath) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", fullcleanpath, err) + } + if common.IsStringPresent(ignoreDirectories, fullcleanpath) { + if common.IsStringPresent(ignoreContents, fullcleanpath) { + return filepath.SkipDir + } + return nil + } + containerizationoptions := containerizers.GetContainerizationOptions(plan, fullpath) + if len(containerizationoptions) == 0 { + log.Debugf("No known containerization approach is supported for %s", fullpath) + if common.IsStringPresent(ignoreContents, fullcleanpath) { + return filepath.SkipDir + } + return nil + } + for _, co := range containerizationoptions { + expandedPath, err := filepath.Abs(fullpath) // If fullpath is "." it will expand to the absolute path. + if err != nil { + log.Warnf("Failed to get the absolute path for %s", fullpath) + continue + } + service := c.newService(filepath.Base(expandedPath)) + service.ContainerBuildType = co.ContainerizationType + service.ContainerizationTargetOptions = co.TargetOptions + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], path) { + service.SourceArtifacts[plantypes.SourceDirectoryArtifactType] = append(service.SourceArtifacts[plantypes.SourceDirectoryArtifactType], path) + service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType] = append(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], path) + } + services = append(services, service) + } + //return nil + return filepath.SkipDir // Skipping all subdirectories when base directory is a valid package + } + return nil + }) + return services, err +} + +// Translate translates artifacts to IR +func (c Any2KubeTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + containerizers := new(containerizer.Containerizers) + containerizers.InitContainerizers(p.Spec.Inputs.RootDir) + for _, service := range services { + if service.TranslationType != c.GetTranslatorType() { + continue + } + log.Debugf("Translating %s", service.ServiceName) + serviceConfig := irtypes.Service{Name: service.ServiceName} + container, err := containerizers.GetContainer(p, service) + if err != nil { + log.Errorf("Unable to translate service %s : %s", service.ServiceName, err) + continue + } + ir.AddContainer(container) + serviceContainer := corev1.Container{Name: service.ServiceName} + serviceContainer.Image = service.Image + serviceConfig.Containers = []corev1.Container{serviceContainer} + ir.Services[service.ServiceName] = serviceConfig + } + return ir, nil +} + +func (c Any2KubeTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.AddSourceType(plantypes.DirectorySourceTypeValue) + service.UpdateContainerBuildPipeline = true + service.UpdateDeployPipeline = true + return service +} + +func (c Any2KubeTranslator) getIgnorePaths(inputPath string) (ignoreDirectories []string, ignoreContents []string) { + ignorefiles, err := common.GetFilesByName(inputPath, []string{"." + types.AppNameShort + "ignore"}) + if err != nil { + log.Warnf("Unable to fetch files to recognize ignore files : %s", err) + } + for _, ignorefile := range ignorefiles { + file, err := os.Open(ignorefile) + if err != nil { + log.Warnf("Failed opening ignore file: %s", err) + continue + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasSuffix(line, "*") { + line = strings.TrimSuffix(line, "*") + path, err := filepath.Abs(filepath.Join(filepath.Dir(ignorefile), line)) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", path, err) + } + ignoreContents = append(ignoreContents, path) + } else { + path, err := filepath.Abs(filepath.Join(filepath.Dir(ignorefile), line)) + if err != nil { + log.Errorf("Unable to resolve full path of directory %s : %s", path, err) + } + ignoreDirectories = append(ignoreDirectories, path) + } + } + } + return ignoreDirectories, ignoreContents +} diff --git a/internal/source/any2kube_test.go b/internal/source/any2kube_test.go new file mode 100644 index 000000000..b997c61c4 --- /dev/null +++ b/internal/source/any2kube_test.go @@ -0,0 +1,401 @@ +/* +Copyright IBM Corporation 2020 + +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 source_test + +import ( + "encoding/base64" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/source" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" + yaml "gopkg.in/yaml.v3" +) + +func TestGetServiceOptions(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get services with a non existent directory and empty plan", func(t *testing.T) { + // Setup + inputpath := "this/does/not/exit/foobar/" + translator := source.Any2KubeTranslator{} + plan := plantypes.NewPlan() + want := []plantypes.Service{} + + // Test + services, err := translator.GetServiceOptions(inputpath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to get the services properly. Expected:", want, "actual:", services) + } + }) + + t.Run("get services with empty directory and empty plan", func(t *testing.T) { + // Setup + inputpath := t.TempDir() + translator := source.Any2KubeTranslator{} + plan := plantypes.NewPlan() + want := []plantypes.Service{} + + // Test + services, err := translator.GetServiceOptions(inputpath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to get the services properly. Expected:", want, "actual:", services) + } + }) + + t.Run("get services when the directory contains files and directories we can't read", func(t *testing.T) { + // Setup + inputpath := t.TempDir() + subdirpath := filepath.Join(inputpath, "nopermstoread") + if err := os.Mkdir(subdirpath, 0); err != nil { + t.Fatal("Failed to create a temporary directory for testing at path", subdirpath, "Error:", err) + } + ignorefilepath := filepath.Join(inputpath, common.IgnoreFilename) + if err := ioutil.WriteFile(ignorefilepath, []byte("foo/"), 0); err != nil { + t.Fatal("Failed to create a temporary file for testing at path", ignorefilepath, "Error:", err) + } + translator := source.Any2KubeTranslator{} + plan := plantypes.NewPlan() + want := []plantypes.Service{} + + // Test + services, err := translator.GetServiceOptions(inputpath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to get the services properly. Expected:", want, "actual:", services) + } + }) + + t.Run("get services from a simple nodejs app and empty plan", func(t *testing.T) { + // Setup + inputPath := "../../samples/nodejs" + translator := source.Any2KubeTranslator{} + + plan := plantypes.NewPlan() + plan.Name = "nodejs-app" + plan.Spec.Inputs.RootDir = inputPath + + want := []plantypes.Service{} + if err := common.ReadYaml("testdata/expectedservicesfornodejsapp.yaml", &want); err != nil { + t.Fatal("Failed to read the expected output services from yaml. Error:", err) + } + + // Test + services, err := translator.GetServiceOptions(inputPath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to create the services properly. Expected:", want, "Actual", services) + } + }) + + t.Run("get services from a simple nodejs app and filled plan", func(t *testing.T) { + // Setup + inputPath := "../../samples/nodejs" + translator := source.Any2KubeTranslator{} + + // services + svc1 := plantypes.NewService("svc1", "Any2Kube") + svc1.SourceArtifacts[plantypes.SourceDirectoryArtifactType] = []string{"foo/"} + svc2 := plantypes.NewService("svc2", "Any2Kube") + svc2.SourceArtifacts[plantypes.SourceDirectoryArtifactType] = []string{"bar/"} + + plan := plantypes.NewPlan() + plan.Name = "nodejs-app" + plan.Spec.Inputs.RootDir = inputPath + plan.Spec.Inputs.Services = map[string][]plantypes.Service{ + "svc1": {svc1}, + "svc2": {svc2}, + } + + want := []plantypes.Service{} + if err := common.ReadYaml("testdata/expectedservicesfornodejsapp.yaml", &want); err != nil { + t.Fatal("Failed to read the expected output services from yaml. Error:", err) + } + + // Test + services, err := translator.GetServiceOptions(inputPath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to create the services properly. Expected:", want, "Actual", services) + } + }) + + t.Run("get services from a simple nodejs app that we already containerized", func(t *testing.T) { + // Setup + inputPath := "../../samples/nodejs" + translator := source.Any2KubeTranslator{} + + // services + svc1 := plantypes.NewService("svc1", "Any2Kube") + svc1.SourceArtifacts[plantypes.SourceDirectoryArtifactType] = []string{"."} + + plan := plantypes.NewPlan() + plan.Name = "nodejs-app" + plan.Spec.Inputs.RootDir = inputPath + plan.Spec.Inputs.Services = map[string][]plantypes.Service{ + "svc1": {svc1}, + } + + want := []plantypes.Service{} + // if err := common.ReadYaml("testdata/expectedservicesfornodejsapp.yaml", &want); err != nil { + // t.Fatal("Failed to read the expected output services from yaml. Error:", err) + // } + + // Test + services, err := translator.GetServiceOptions(inputPath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to create the services properly. Expected:", want, "Actual", services) + } + }) + + t.Run("test m2kignore can ignore a directory but include its subdirectories", func(t *testing.T) { + // 1. Ignore a directory, but include all subdirectories + + // Setup + inputPath := "testdata/nodejsappwithm2kignorecase1" + translator := source.Any2KubeTranslator{} + + plan := plantypes.NewPlan() + plan.Name = "nodejs-app" + plan.Spec.Inputs.RootDir = inputPath + + want := []plantypes.Service{} + if err := common.ReadYaml("testdata/expectedservicesfornodejsappwithm2kignorecase1.yaml", &want); err != nil { + t.Fatal("Failed to read the expected output services from yaml. Error:", err) + } + + // Test + services, err := translator.GetServiceOptions(inputPath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to create the services properly. Expected:", want, "Actual", services) + } + }) + + t.Run("test m2kignore can be used to ignore everything but a very specific subdirectory", func(t *testing.T) { + // Setup + inputPath := "testdata/javamavenappwithm2kignorecase2" + translator := source.Any2KubeTranslator{} + + plan := plantypes.NewPlan() + plan.Name = "nodejs-app" + plan.Spec.Inputs.RootDir = inputPath + + want := []plantypes.Service{} + if err := common.ReadYaml("testdata/expectedservicesforjavamavenappwithm2kignorecase2.yaml", &want); err != nil { + t.Fatal("Failed to read the expected output services from yaml. Error:", err) + } + + // Test + services, err := translator.GetServiceOptions(inputPath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + // if err := common.WriteYaml("testdata/hmm.yaml", services); err != nil { + // t.Fatal("error", err) + // } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to create the services properly. Expected:", want, "Actual", services) + } + }) + + t.Run("test m2kignore can include a directory but ignore all subdirectories", func(t *testing.T) { + // 2. Include a directory, ignore all subdirectories or a subset of subdirectories + // TODO: Note that while m2kignore might work as expected, the buildpacks do not. + // The CNB buildpacks when run on a directory will ALWAYS look inside all of its subdirectories as well. + + // Setup + // We create the following directory structure: + // . + inputpath := t.TempDir() + // ./includeme/ + // ./includeme/excludeme/ + subdirpath := filepath.Join(inputpath, "includeme") + subsubdirpath := filepath.Join(subdirpath, "excludeme") + if err := os.MkdirAll(subsubdirpath, common.DefaultDirectoryPermission); err != nil { + t.Fatal("Failed to create a temporary directory for testing at path", subsubdirpath, "Error:", err) + } + // .m2kignore + testdatapath := "testdata/m2kignoreforignorecontents" + ignorerules, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatal("Failed to read the testdata at", testdatapath, "Error:", err) + } + ignorefilepath := filepath.Join(inputpath, common.IgnoreFilename) + if err := ioutil.WriteFile(ignorefilepath, ignorerules, common.DefaultFilePermission); err != nil { + t.Fatal("Failed to create a temporary file for testing at path", ignorefilepath, "Error:", err) + } + // ./includeme/excludeme/index.php + fpath := filepath.Join(subsubdirpath, "package.json") + if err := ioutil.WriteFile(fpath, []byte("this is ' invalid json"), common.DefaultFilePermission); err != nil { + t.Fatal("Failed to create a temporary file for testing at path", fpath, "Error:", err) + } + + translator := source.Any2KubeTranslator{} + plan := plantypes.NewPlan() + want := []plantypes.Service{} + + // Test + services, err := translator.GetServiceOptions(inputpath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to get the services properly. Expected:", want, "actual:", services) + } + }) + + t.Run("test multiple hierarchical m2kignores", func(t *testing.T) { + // TODO: Note that while m2kignore might work as expected, the buildpacks do not. + // The CNB buildpacks when run on a directory will ALWAYS look inside all of its subdirectories as well. + // The behaviour of the CNB buildpacks makes it virtually impossible to test this scenario. + // This test can really only be checked through vscode debugging to make sure the correct directories are being ignored. + + // Setup + // We create the following directory structure: + /* + . + ├── .m2kignore + ├── a + │   └── a + │   ├── .m2kignore + │   ├── a + │   │   ├── a + │   │   ├── b + │   │   ├── c + │   │   └── d + │   └── b + │   ├── a + │   └── b + ├── b + │   ├── .m2kignore + │   └── a + │   ├── a + │   └── b + └── c + └── a + */ + tempdir := t.TempDir() + testdatapath := "testdata/testmultiplem2kignores.tar" + + tarbytes, err := ioutil.ReadFile(testdatapath) + if err != nil { + t.Fatalf("Failed to read the test data at path %q Error: %q", testdatapath, err) + } + + tarstring := base64.StdEncoding.EncodeToString(tarbytes) + err = common.UnTarString(tarstring, tempdir) + if err != nil { + t.Fatalf("Failed to untar the test data into path %q Error: %q", tempdir, err) + } + + inputpath := filepath.Join(tempdir, "testmultiplem2kignores") + translator := source.Any2KubeTranslator{} + plan := plantypes.NewPlan() + want := []plantypes.Service{} + + // Test + services, err := translator.GetServiceOptions(inputpath, plan) + if err != nil { + t.Fatal("Failed to get the services. Error:", err) + } + if !reflect.DeepEqual(services, want) { + t.Fatal("Failed to get the services properly. Expected:", want, "actual:", services) + } + }) +} + +func TestTranslate(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get intermediate representation with no services and an empty plan", func(t *testing.T) { + // Setup + // inputpath := "this/does/not/exit/foobar/" + translator := source.Any2KubeTranslator{} + services := []plantypes.Service{} + plan := plantypes.NewPlan() + want := irtypes.NewIR(plan) + + // Test + ir, err := translator.Translate(services, plan) + if err != nil { + t.Fatal("Failed to get the intermediate representation. Error:", err) + } + if !reflect.DeepEqual(ir, want) { + t.Fatal("Failed to get the intermediate representation properly. Expected:", want, "actual:", ir) + } + }) + + t.Run("get intermediate representation with some services and an empty plan", func(t *testing.T) { + // Setup + translator := source.Any2KubeTranslator{} + + // Input + services := []plantypes.Service{} + testdataservices := "testdata/datafortestingtranslate/servicesfromnodejsapp.yaml" + if err := common.ReadYaml(testdataservices, &services); err != nil { + t.Fatalf("Failed to read the testdata at path %q Error: %q", testdataservices, err) + } + plan := plantypes.NewPlan() + + // Output + testdataoutput := "testdata/datafortestingtranslate/expectedirfornodejsapp.yaml" + wantbytes, err := ioutil.ReadFile(testdataoutput) + if err != nil { + t.Fatalf("Failed to read the testdata at path %q Error: %q", testdataoutput, err) + } + wantyaml := string(wantbytes) + + // Test + ir, err := translator.Translate(services, plan) + if err != nil { + t.Fatal("Failed to get the intermediate representation. Error:", err) + } + irbytes, err := yaml.Marshal(ir) + if err != nil { + t.Fatal("Failed to marshal the intermediate representation to yaml for comparison. Error:", err) + } + iryaml := string(irbytes) + if iryaml != wantyaml { + t.Fatal("Failed to get the intermediate representation properly. Expected:", wantyaml, "actual:", iryaml) + } + }) +} diff --git a/internal/source/cfmanifest2kube.go b/internal/source/cfmanifest2kube.go new file mode 100644 index 000000000..45874143b --- /dev/null +++ b/internal/source/cfmanifest2kube.go @@ -0,0 +1,473 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + "io/ioutil" + "path/filepath" + "strings" + + "code.cloudfoundry.org/cli/util/manifest" + "github.com/cloudfoundry/bosh-cli/director/template" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + v1 "k8s.io/api/core/v1" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + "github.com/konveyor/move2kube/internal/source/data" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +//go:generate go run github.com/konveyor/move2kube/internal/common/generator data + +// CfManifestTranslator implements Translator interface for CfManifest files +type CfManifestTranslator struct { +} + +// GetTranslatorType returns the translator type +func (c CfManifestTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.CfManifest2KubeTranslation +} + +// GetServiceOptions - output a plan based on the input directory contents +func (c CfManifestTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + services := make([]plantypes.Service, 0) + allcontainerizers := new(containerizer.Containerizers) + allcontainerizers.InitContainerizers(inputPath) + containerizers := collecttypes.CfContainerizers{} + + var files, err = common.GetFilesByExt(inputPath, []string{".yml", ".yaml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize cf manifest yamls : %s", err) + } + // Load buildpack mappings, if available + containerizersmetadata := collecttypes.CfContainerizers{} + err = yaml.Unmarshal([]byte(data.Cfbuildpacks_yaml), &containerizersmetadata) + if err != nil { + log.Debugf("Not valid containerizer option data : %s", err) + } + containerizers.Spec.BuildpackContainerizers = append(containerizers.Spec.BuildpackContainerizers, containerizersmetadata.Spec.BuildpackContainerizers...) + for _, fullpath := range files { + containerizersmetadata := collecttypes.CfContainerizers{} + err := common.ReadYaml(fullpath, &containerizersmetadata) + if err != nil { + log.Debugf("Not a valid containerizer option file : %s %s", fullpath, err) + continue + } + containerizers.Spec.BuildpackContainerizers = append(containerizers.Spec.BuildpackContainerizers, containerizersmetadata.Spec.BuildpackContainerizers...) + } + log.Debugf("Containerizers %+v", containerizers) + + // Load instance apps, if available + cfinstanceapps := map[string][]collecttypes.CfApplication{} //path + for _, fullpath := range files { + cfinstanceappsfile := collecttypes.CfInstanceApps{} + err := common.ReadYaml(fullpath, &cfinstanceappsfile) + if err != nil || cfinstanceappsfile.Kind != string(collecttypes.CfInstanceAppsMetadataKind) { + log.Debugf("Not a valid apps file : %s %s", fullpath, err) + continue + } + relpath, _ := plan.GetRelativePath(fullpath) + cfinstanceapps[relpath] = append(cfinstanceapps[relpath], cfinstanceappsfile.Spec.CfApplications...) + } + log.Debugf("Cf Instances %+v", cfinstanceapps) + + appsCovered := []string{} + + for _, fullpath := range files { + applications, _, err := ReadApplicationManifest(fullpath, "", plantypes.Yamls) + if err != nil { + log.Debugf("Error while trying to parse manifest : %s", err) + continue + } + path, _ := plan.GetRelativePath(fullpath) + for _, application := range applications { + containerizationoptionsfound := false + fullbuilddirectory := filepath.Dir(fullpath) + if application.Path != "" { + fullbuilddirectory = filepath.Join(fullpath, application.Path) + } + applicationName := application.Name + if applicationName == "" { + basename := filepath.Base(fullpath) + applicationName = strings.TrimSuffix(basename, filepath.Ext(basename)) + } + appinstancefilepath, appinstance := getCfInstanceApp(cfinstanceapps, applicationName) + builddirectory, _ := plan.GetRelativePath(fullbuilddirectory) + if application.DockerImage != "" || appinstance.DockerImage != "" { + service := c.newService(applicationName) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + if application.DockerImage != "" { + service.Image = application.DockerImage + } else { + service.Image = appinstance.DockerImage + } + service.UpdateContainerBuildPipeline = false + services = append(services, service) + appsCovered = append(appsCovered, applicationName) + containerizationoptionsfound = true + } else { + for _, cop := range allcontainerizers.GetContainerizationOptions(plan, fullbuilddirectory) { + service := c.newService(applicationName) + service.ContainerBuildType = cop.ContainerizationType + service.ContainerizationTargetOptions = cop.TargetOptions + service.AddSourceArtifact(plantypes.CfManifestArtifactType, path) + if appinstance.Name != "" { + service.AddSourceArtifact(plantypes.CfRunningManifestArtifactType, appinstancefilepath) + } + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + services = append(services, service) + appsCovered = append(appsCovered, applicationName) + containerizationoptionsfound = true + } + for _, containerizer := range containerizers.Spec.BuildpackContainerizers { + isbuildpackmatched := false + if application.Buildpack.IsSet && containerizer.BuildpackName == application.Buildpack.Value { + isbuildpackmatched = true + } + for _, bpname := range application.Buildpacks { + if bpname == containerizer.BuildpackName { + isbuildpackmatched = true + break + } + } + if !isbuildpackmatched { + if (appinstance.Buildpack != "" && containerizer.BuildpackName == appinstance.Buildpack) || (appinstance.DetectedBuildpack != "" && containerizer.BuildpackName == appinstance.DetectedBuildpack) { + isbuildpackmatched = true + } + } + if isbuildpackmatched { + service := c.newService(applicationName) + service.ContainerBuildType = containerizer.ContainerBuildType + service.ContainerizationTargetOptions = containerizer.ContainerizationTargetOptions + service.AddSourceArtifact(plantypes.CfManifestArtifactType, path) + if appinstance.Name != "" { + service.AddSourceArtifact(plantypes.CfRunningManifestArtifactType, appinstancefilepath) + } + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + services = append(services, service) + appsCovered = append(appsCovered, applicationName) + containerizationoptionsfound = true + } + } + if !containerizationoptionsfound { + log.Warnf("No known containerization approach for %s even though it has a cf manifest %s; Defaulting to manual", fullbuilddirectory, filepath.Base(fullpath)) + service := c.newService(applicationName) + service.ContainerBuildType = plantypes.ManualContainerBuildTypeValue + service.AddSourceArtifact(plantypes.CfManifestArtifactType, path) + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + appsCovered = append(appsCovered, applicationName) + services = append(services, service) + } + } + } + + for appfilepath, apps := range cfinstanceapps { + for _, application := range apps { + applicationName := application.Name + if !common.IsStringPresent(appsCovered, applicationName) { + containerizationoptionsfound := false + fullbuilddirectory := filepath.Dir(appfilepath) + applicationName := application.Name + if applicationName == "" { + continue + } + builddirectory, _ := plan.GetRelativePath(fullbuilddirectory) + if application.DockerImage != "" { + service := c.newService(applicationName) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + if application.DockerImage != "" { + service.Image = application.DockerImage + } + service.UpdateContainerBuildPipeline = false + services = append(services, service) + containerizationoptionsfound = true + } else { + //TODO: Think whether we should include this for only runtime manifest file + for _, cop := range allcontainerizers.GetContainerizationOptions(plan, fullbuilddirectory) { + service := c.newService(applicationName) + service.ContainerBuildType = cop.ContainerizationType + service.ContainerizationTargetOptions = cop.TargetOptions + service.AddSourceArtifact(plantypes.CfRunningManifestArtifactType, appfilepath) + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + services = append(services, service) + containerizationoptionsfound = true + } + for _, containerizer := range containerizers.Spec.BuildpackContainerizers { + isbuildpackmatched := false + if !isbuildpackmatched { + if (application.Buildpack != "" && containerizer.BuildpackName == application.Buildpack) || (application.DetectedBuildpack != "" && containerizer.BuildpackName == application.DetectedBuildpack) { + isbuildpackmatched = true + } + } + if isbuildpackmatched { + service := c.newService(applicationName) + service.ContainerBuildType = containerizer.ContainerBuildType + service.ContainerizationTargetOptions = containerizer.ContainerizationTargetOptions + service.AddSourceArtifact(plantypes.CfRunningManifestArtifactType, appfilepath) + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + services = append(services, service) + containerizationoptionsfound = true + } + } + if !containerizationoptionsfound { + log.Warnf("No known containerization approach for %s even though it has a cf manifest %s; Defaulting to manual", fullbuilddirectory, filepath.Base(fullpath)) + service := c.newService(applicationName) + service.ContainerBuildType = plantypes.ManualContainerBuildTypeValue + service.AddSourceArtifact(plantypes.CfRunningManifestArtifactType, appfilepath) + if !common.IsStringPresent(service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType], builddirectory) { + service.AddSourceArtifact(plantypes.SourceDirectoryArtifactType, builddirectory) + service.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, builddirectory) + } + services = append(services, service) + } + } + + } + } + } + } + return services, nil +} + +// Translate translates servies to IR +func (c CfManifestTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + containerizers := new(containerizer.Containerizers) + containerizers.InitContainerizers(p.Spec.Inputs.RootDir) + for _, service := range services { + if service.TranslationType == c.GetTranslatorType() { + log.Debugf("Translating %s", service.ServiceName) + + var cfinstanceapp collecttypes.CfApplication + if runninginstancefile, ok := service.SourceArtifacts[plantypes.CfRunningManifestArtifactType]; ok { + cfinstanceapp = getCfAppInstance(p.GetFullPath(runninginstancefile[0]), service.ServiceName) + } + + if paths, ok := service.SourceArtifacts[plantypes.CfManifestArtifactType]; ok { + path := paths[0] + applications, variables, err := ReadApplicationManifest(p.GetFullPath(path), service.ServiceName, p.Spec.Outputs.Kubernetes.ArtifactType) + if err != nil { + log.Debugf("Error while trying to parse manifest : %s", err) + continue + } + application := applications[0] + serviceConfig := irtypes.Service{Name: service.ServiceName} + serviceContainer := v1.Container{Name: service.ServiceName} + serviceContainer.Image = service.Image + //TODO: Add support for services, health check, memory + if application.Instances.IsSet { + serviceConfig.Replicas = application.Instances.Value + } else if cfinstanceapp.Instances != 0 { + serviceConfig.Replicas = cfinstanceapp.Instances + } + for varname, value := range application.EnvironmentVariables { + envvar := v1.EnvVar{Name: varname, Value: value} + serviceContainer.Env = append(serviceContainer.Env, envvar) + } + for varname, value := range cfinstanceapp.Env { + envvar := v1.EnvVar{Name: varname, Value: value} + serviceContainer.Env = append(serviceContainer.Env, envvar) + } + for _, variable := range variables { + ir.Values.GlobalVariables[variable] = variable + } + if cfinstanceapp.Ports != nil && len(cfinstanceapp.Ports) > 0 { + serviceContainer.Ports = []v1.ContainerPort{} + for _, port := range cfinstanceapp.Ports { + serviceContainer.Ports = append(serviceContainer.Ports, v1.ContainerPort{ContainerPort: port, + Protocol: v1.ProtocolTCP, + }) + } + envvar := v1.EnvVar{Name: "PORT", Value: string(cfinstanceapp.Ports[0])} + serviceContainer.Env = append(serviceContainer.Env, envvar) + } else { + serviceContainer.Ports = []v1.ContainerPort{ + {ContainerPort: 8080, + Protocol: v1.ProtocolTCP, + }} + envvar := v1.EnvVar{Name: "PORT", Value: "8080"} + serviceContainer.Env = append(serviceContainer.Env, envvar) + } + serviceConfig.Containers = []v1.Container{serviceContainer} + container, err := containerizers.GetContainer(p, service) + if err == nil { + ir.AddContainer(container) + ir.Services[service.ServiceName] = serviceConfig + } else { + log.Errorf("Unable to translate service %s using cfmanifest at %s : %s", service.ServiceName, path, err) + } + } else { + serviceConfig := irtypes.Service{Name: service.ServiceName} + serviceContainer := v1.Container{Name: service.ServiceName} + serviceContainer.Image = service.Image + if cfinstanceapp.Instances != 0 { + serviceConfig.Replicas = cfinstanceapp.Instances + } + for varname, value := range cfinstanceapp.Env { + envvar := v1.EnvVar{Name: varname, Value: value} + serviceContainer.Env = append(serviceContainer.Env, envvar) + } + if cfinstanceapp.Ports != nil && len(cfinstanceapp.Ports) > 0 { + serviceContainer.Ports = []v1.ContainerPort{} + for _, port := range cfinstanceapp.Ports { + serviceContainer.Ports = append(serviceContainer.Ports, v1.ContainerPort{ContainerPort: port, + Protocol: v1.ProtocolTCP, + }) + } + } else { + serviceContainer.Ports = []v1.ContainerPort{ + { + ContainerPort: 8080, + Protocol: v1.ProtocolTCP, + }} + } + serviceConfig.Containers = []v1.Container{serviceContainer} + container, err := containerizers.GetContainer(p, service) + if err == nil { + ir.AddContainer(container) + ir.Services[service.ServiceName] = serviceConfig + } else { + log.Errorf("Unable to translate service %s using cfinstancemanifest : %s", service.ServiceName, err) + } + } + } + } + return ir, nil +} + +func (c CfManifestTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.AddSourceType(plantypes.DirectorySourceTypeValue) + service.AddSourceType(plantypes.CfManifestSourceTypeValue) + service.UpdateContainerBuildPipeline = true + service.UpdateDeployPipeline = true + return service +} + +// ReadApplicationManifest reads an application manifest +func ReadApplicationManifest(path string, serviceName string, artifactType plantypes.TargetArtifactTypeValue) ([]manifest.Application, []string, error) { // manifest, parameters + trimmedvariables, err := getMissingVariables(path) + if err != nil { + log.Debugf("Unable to read as cf manifest %s : %s", path, err) + return []manifest.Application{}, []string{}, err + } + + rawManifest, err := ioutil.ReadFile(path) + if err != nil { + log.Errorf("Unable to read manifest file %s", path) + return []manifest.Application{}, []string{}, err + } + tpl := template.NewTemplate(rawManifest) + fileVars := template.StaticVariables{} + for _, variable := range trimmedvariables { + if artifactType == plantypes.Helm { + fileVars[variable] = "{{ index .Values " + `"globalvariables" "` + variable + `"}}` + } else { + fileVars[variable] = "{{ $" + variable + " }}" + } + } + rawManifest, err = tpl.Evaluate(fileVars, nil, template.EvaluateOpts{ExpectAllKeys: true}) + if err != nil { + log.Errorf("Interpolation Error %s", err) + return []manifest.Application{}, []string{}, err + } + + var m manifest.Manifest + err = yaml.Unmarshal(rawManifest, &m) + if err != nil { + log.Errorf("UnMarshalling error %s", err) + return []manifest.Application{}, []string{}, err + } + if len(m.Applications) == 1 { + //If the service name is missing, use the directory name + return m.Applications, trimmedvariables, nil + } + applications := []manifest.Application{} + if serviceName != "" { + for _, application := range m.Applications { + if application.Name == serviceName { + applications = append(applications, application) + } + } + } else { + applications = m.Applications + } + return applications, trimmedvariables, nil +} + +func getMissingVariables(path string) ([]string, error) { + trimmedvariables := []string{} + _, err := manifest.ReadAndInterpolateManifest(path, []string{}, []template.VarKV{}) + if err != nil { + errstring := err.Error() + if strings.Contains(errstring, "Expected to find variables:") { + variablesstr := strings.Split(errstring, ":")[1] + variables := strings.Split(variablesstr, ",") + for _, variable := range variables { + trimmedvariables = append(trimmedvariables, strings.TrimSpace(variable)) + } + } else { + log.Debugf("Error %s", err) + return []string{}, err + } + } + return trimmedvariables, nil +} + +func getCfInstanceApp(apps map[string][]collecttypes.CfApplication, name string) (string, collecttypes.CfApplication) { + for path, apps := range apps { + for _, app := range apps { + if app.Name == name { + return path, app + } + } + } + return "", collecttypes.CfApplication{} +} + +func getCfAppInstance(path string, appname string) collecttypes.CfApplication { + cfinstanceappsfile := collecttypes.CfInstanceApps{} + err := common.ReadYaml(path, &cfinstanceappsfile) + if err != nil { + log.Debugf("Not a valid apps file : %s %s", path, err) + } + for _, app := range cfinstanceappsfile.Spec.CfApplications { + if app.Name == appname { + return app + } + } + return collecttypes.CfApplication{} +} diff --git a/internal/source/compose/utils.go b/internal/source/compose/utils.go new file mode 100644 index 000000000..d2ee5dc31 --- /dev/null +++ b/internal/source/compose/utils.go @@ -0,0 +1,58 @@ +/* +Copyright IBM Corporation 2020 + +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" + "strings" + + corev1 "k8s.io/api/core/v1" +) + +const ( + modeReadOnly string = "ro" + tmpFsPath string = "tmpfs" + defaultSecretBasePath string = "/var/secrets" + envFile string = "env_file" +) + +func makeVolumesFromTmpFS(serviceName string, tfsList []string) ([]corev1.VolumeMount, []corev1.Volume) { + var vmList []corev1.VolumeMount + var vList []corev1.Volume + + for index, tfsObj := range tfsList { + volumeName := fmt.Sprintf("%s-%s-%d", serviceName, tmpFsPath, index) + + vmList = append(vmList, corev1.VolumeMount{ + Name: volumeName, + MountPath: strings.Split(tfsObj, ":")[0], + }) + + vList = append(vList, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, + }, + }) + } + + return vmList, vList +} + +func isPath(substring string) bool { + return strings.Contains(substring, "/") || substring == "." +} diff --git a/internal/source/compose/v1v2.go b/internal/source/compose/v1v2.go new file mode 100755 index 000000000..4eb10657e --- /dev/null +++ b/internal/source/compose/v1v2.go @@ -0,0 +1,389 @@ +/* +Copyright IBM Corporation 2020 + +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" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/lookup" + "github.com/docker/libcompose/project" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// V1V2Loader loads a compoose file of versions 1 or 2 +type V1V2Loader struct { +} + +// ConvertToIR loads a compose file to IR +func (c *V1V2Loader) ConvertToIR(composefilepath string, serviceName string) (ir irtypes.IR, err error) { + filedata, err := ioutil.ReadFile(composefilepath) + if err != nil { + return + } + re := regexp.MustCompile(`(?s)\n\s+env_file:.*?(\n\s*[a-zA-Z]|$)`) + envFileStrings := re.FindAllString(string(filedata), -1) + for _, envFileString := range envFileStrings { + lines := strings.Split(envFileString, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "-") { + line = strings.TrimSpace(strings.TrimPrefix(line, "-")) + envfilere := regexp.MustCompile(`[^\s+]+`) + envfile := envfilere.FindString(line) + if !filepath.IsAbs(envfile) { + envfile = filepath.Join(filepath.Dir(composefilepath), envfile) + } + _, err := os.Stat(envfile) + if os.IsNotExist(err) { + log.Warnf("Unable to find env config file %s referred in service %s in file %s. Ignoring it.", envfile, serviceName, composefilepath) + err = ioutil.WriteFile(envfile, []byte{}, common.DefaultFilePermission) + if err != nil { + log.Errorf("Unable to write temp env file %s : %s", envfile, err) + } else { + defer os.Remove(envfile) + } + } + } + } + } + + context := &project.Context{} + context.ComposeFiles = []string{composefilepath} + if context.ResourceLookup == nil { + context.ResourceLookup = &lookup.FileResourceLookup{} + } + if context.EnvironmentLookup == nil { + //TODO: Check if any variable is mandatory + cwd := "" + if !common.IgnoreEnvironment { + cwd, err = os.Getwd() + if err != nil { + return irtypes.IR{}, nil + } + } + context.EnvironmentLookup = &lookup.ComposableEnvLookup{ + Lookups: []config.EnvironmentLookup{ + &lookup.EnvfileLookup{ + Path: filepath.Join(cwd, ".env"), + }, + &lookup.OsEnvLookup{}, + }, + } + } + proj := project.NewProject(context, nil, nil) + err = proj.Parse() + if err != nil { + log.Errorf("Failed to load compose file %s : %s", composefilepath, err) + return irtypes.IR{}, errors.Wrap(err, "Failed to load compose file") + } + ir, err = c.convertToIR(filepath.Dir(composefilepath), proj, serviceName) + if err != nil { + return irtypes.IR{}, err + } + + return ir, nil +} + +func (c *V1V2Loader) convertToIR(filedir string, composeObject *project.Project, serviceName string) (ir irtypes.IR, err error) { + ir = irtypes.IR{ + Services: make(map[string]irtypes.Service), + } + + for name, composeServiceConfig := range composeObject.ServiceConfigs.All() { + serviceConfig := irtypes.Service{Name: common.NormalizeForServiceName(name)} + if serviceName != serviceConfig.Name { + continue + } + serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) + if composeServiceConfig.Hostname != "" { + serviceConfig.Hostname = composeServiceConfig.Hostname + } + if composeServiceConfig.DomainName != "" { + serviceConfig.Subdomain = composeServiceConfig.DomainName + } + serviceContainer := corev1.Container{} + serviceContainer.Image = composeServiceConfig.Image + if serviceContainer.Image == "" { + serviceContainer.Image = name + ":latest" + } + if composeServiceConfig.Build.Dockerfile != "" && composeServiceConfig.Build.Context != "" { + //TODO: Add support for args and labels + c := containerizer.ReuseDockerfileContainerizer{} + con, err := c.GetContainer(filedir, name, serviceContainer.Image, composeServiceConfig.Build.Dockerfile, composeServiceConfig.Build.Context) + if err != nil { + log.Warnf("Unable to get containization script even though build parameters are present : %s", err) + } else { + ir.AddContainer(con) + } + } + serviceContainer.Name = strings.ToLower(composeServiceConfig.ContainerName) + if serviceContainer.Name != composeServiceConfig.ContainerName { + log.Debugf("Container name in service %q has been changed from %q to %q", name, composeServiceConfig.ContainerName, serviceContainer.Name) + } + if serviceContainer.Name == "" { + serviceContainer.Name = serviceConfig.Name + } + serviceContainer.Command = composeServiceConfig.Entrypoint + serviceContainer.Args = composeServiceConfig.Command + serviceContainer.Env = c.getEnvs(composeServiceConfig.Environment) + serviceContainer.WorkingDir = composeServiceConfig.WorkingDir + serviceContainer.Stdin = composeServiceConfig.StdinOpen + serviceContainer.TTY = composeServiceConfig.Tty + ports := c.getPorts(composeServiceConfig.Ports, composeServiceConfig.Expose) + serviceContainer.Ports = ports + podSecurityContext := &corev1.PodSecurityContext{} + securityContext := &corev1.SecurityContext{} + if composeServiceConfig.Privileged { + securityContext.Privileged = &composeServiceConfig.Privileged + } + if composeServiceConfig.User != "" { + uid, err := strconv.ParseInt(composeServiceConfig.User, 10, 64) + if err != nil { + log.Warn("Ignoring user directive. User to be specified as a UID (numeric).") + } else { + securityContext.RunAsUser = &uid + } + + } + capsAdd := []corev1.Capability{} + capsDrop := []corev1.Capability{} + for _, capAdd := range composeServiceConfig.CapAdd { + capsAdd = append(capsAdd, corev1.Capability(capAdd)) + } + for _, capDrop := range composeServiceConfig.CapDrop { + capsDrop = append(capsDrop, corev1.Capability(capDrop)) + } + if len(capsAdd) > 0 || len(capsDrop) > 0 { + securityContext.Capabilities = &corev1.Capabilities{ + Add: capsAdd, + Drop: capsDrop, + } + } + if *securityContext != (corev1.SecurityContext{}) { + serviceContainer.SecurityContext = securityContext + } + if !reflect.DeepEqual(*podSecurityContext, corev1.PodSecurityContext{}) { + serviceConfig.SecurityContext = podSecurityContext + } + // group should be in gid format not group name + groupAdd, err := getGroupAdd(composeServiceConfig.GroupAdd) + if err != nil { + log.Warnf("GroupAdd should be in gid format, not as group name : %s", err) + } + if groupAdd != nil { + podSecurityContext.SupplementalGroups = groupAdd + } + if composeServiceConfig.StopGracePeriod != "" { + serviceConfig.TerminationGracePeriodSeconds, err = durationInSeconds(composeServiceConfig.StopGracePeriod) + if err != nil { + log.Warnf("Failed to parse duration %v for service %v", composeServiceConfig.StopGracePeriod, name) + } + } + if composeServiceConfig.MemLimit != 0 { + resourceLimit := corev1.ResourceList{} + if composeServiceConfig.MemLimit != 0 { + resourceLimit[corev1.ResourceMemory] = *resource.NewQuantity(int64(composeServiceConfig.MemLimit), "RandomStringForFormat") + } + serviceContainer.Resources.Limits = resourceLimit + } + + restart := composeServiceConfig.Restart + if restart == "unless-stopped" { + log.Warnf("Restart policy 'unless-stopped' in service %s is not supported, convert it to 'always'", name) + serviceConfig.RestartPolicy = corev1.RestartPolicyAlways + } + + if composeServiceConfig.Networks != nil && len(composeServiceConfig.Networks.Networks) > 0 { + for _, value := range composeServiceConfig.Networks.Networks { + if value.Name != "default" { + serviceConfig.Networks = append(serviceConfig.Networks, value.RealName) + } + } + } + + vml, vl := makeVolumesFromTmpFS(name, composeServiceConfig.Tmpfs) + for _, v := range vl { + serviceConfig.AddVolume(v) + } + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, vml...) + + if composeServiceConfig.VolumesFrom != nil { + log.Warnf("Ignoring VolumeFrom in compose for service %s : %s", serviceName, composeServiceConfig.VolumesFrom) + } + + if composeServiceConfig.Volumes != nil { + for index, vol := range composeServiceConfig.Volumes.Volumes { + if isPath(vol.Source) { + volumeName := fmt.Sprintf("%s%d", common.VolumePrefix, index) + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + ReadOnly: vol.AccessMode == modeReadOnly, + MountPath: vol.Destination, + }) + + serviceConfig.AddVolume(corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Path: vol.Source}, + }, + }) + } else { + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, corev1.VolumeMount{ + Name: vol.Source, + ReadOnly: vol.AccessMode == modeReadOnly, + MountPath: vol.Destination, + }) + + serviceConfig.AddVolume(corev1.Volume{ + Name: vol.Source, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: vol.Source, + ReadOnly: vol.AccessMode == modeReadOnly, + }, + }, + }) + accessMode := corev1.ReadWriteMany + if vol.AccessMode == modeReadOnly { + accessMode = corev1.ReadOnlyMany + } + storageObj := irtypes.Storage{StorageType: irtypes.PVCKind, Name: vol.Source, Content: nil} + storageObj.PersistentVolumeClaimSpec = corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{accessMode}, + } + ir.AddStorage(storageObj) + } + } + } + + serviceConfig.Containers = []v1.Container{serviceContainer} + ir.Services[name] = serviceConfig + } + + return ir, nil +} + +func (c *V1V2Loader) getEnvs(envars []string) []corev1.EnvVar { + envs := []corev1.EnvVar{} + for _, e := range envars { + m := regexp.MustCompile(`[=:]`) + locs := m.FindStringIndex(e) + if locs == nil || len(locs) < 1 { + envs = append(envs, corev1.EnvVar{ + Name: e, + Value: "unknown", + }) + } else { + envs = append(envs, corev1.EnvVar{ + Name: e[locs[0]+1:], + Value: e[:locs[0]], + }) + } + } + return envs +} + +func (c *V1V2Loader) getPorts(composePorts []string, expose []string) []corev1.ContainerPort { + ports := []corev1.ContainerPort{} + exist := map[int32]bool{} + for _, port := range composePorts { + cp := c.getContainerPort(port) + if !exist[cp.ContainerPort] && cp != (corev1.ContainerPort{}) { + ports = append(ports, cp) + exist[cp.ContainerPort] = true + } + } + for _, port := range expose { + cp := c.getContainerPort(port) + if !exist[cp.ContainerPort] && cp != (corev1.ContainerPort{}) { + ports = append(ports, cp) + exist[cp.ContainerPort] = true + } + } + + return ports +} + +func (c *V1V2Loader) getContainerPort(value string) (cp corev1.ContainerPort) { + // 15000:15000/tcp Default protocol TCP + proto := corev1.ProtocolTCP + parts := strings.Split(value, "/") + if len(parts) == 2 && strings.EqualFold(string(corev1.ProtocolUDP), parts[1]) { + proto = corev1.ProtocolUDP + } + // Split up the ports and IP + justPorts := strings.Split(parts[0], ":") + if len(justPorts) > 0 { + // ex. 127.0.0.1:80:80 + // Get the container port + portStr := justPorts[len(justPorts)-1] + p, err := strconv.Atoi(portStr) + if err != nil { + log.Warnf("Invalid container port in %s ; Example: 127.0.0.1:80:80 or 80:80 or 80", parts[0]) + } else { + cp = corev1.ContainerPort{ + ContainerPort: int32(p), + Protocol: proto, + } + } + } + return +} + +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") + } + groupAdd = append(groupAdd, int64(j)) + + } + return groupAdd, nil +} + +func durationInSeconds(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 +} diff --git a/internal/source/compose/v3.go b/internal/source/compose/v3.go new file mode 100755 index 000000000..d67276596 --- /dev/null +++ b/internal/source/compose/v3.go @@ -0,0 +1,607 @@ +/* +Copyright IBM Corporation 2020 + +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" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/spf13/cast" + + "github.com/docker/cli/cli/compose/loader" + "github.com/docker/cli/cli/compose/types" + libcomposeyaml "github.com/docker/libcompose/yaml" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/konveyor/move2kube/internal/containerizer" + + "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// V3Loader loads a v3 compose file +type V3Loader struct { +} + +// ConvertToIR loads an v3 compose file into IR +func (c *V3Loader) ConvertToIR(composefilepath string, serviceName string) (irtypes.IR, error) { + loadedFile, err := ioutil.ReadFile(composefilepath) + if err != nil { + log.Warnf("Unable to load Compose file : %s", err) + return irtypes.IR{}, err + } + // Parse the Compose File + parsedComposeFile, err := loader.ParseYAML(loadedFile) + if err != nil { + log.Warnf("Unable to parse Compose file : %s", err) + return irtypes.IR{}, err + } + // Remove unresolvable env files, so that the parser does not throw error + if val, ok := parsedComposeFile["services"]; ok { + if services, ok := val.(map[string]interface{}); ok { + if val, ok := services[serviceName]; ok { + if vals, ok := val.(map[string]interface{}); ok { + envfiles := make([]interface{}, 0) + if envfilesvals, ok := vals[envFile]; ok { + if envfilesvalsint, ok := envfilesvals.([]interface{}); ok { + for _, envfilesval := range envfilesvalsint { + if envfilesstr, ok := envfilesval.(string); ok { + path := envfilesstr + if !filepath.IsAbs(path) { + path = filepath.Join(filepath.Dir(composefilepath), path) + } + info, err := os.Stat(path) + if os.IsNotExist(err) || info.IsDir() { + log.Warnf("Unable to find env config file %s referred in service %s in file %s. Ignoring it.", path, serviceName, composefilepath) + } else { + envfiles = append(envfiles, envfilesstr) + } + } + } + } + } + vals[envFile] = envfiles + services = make(map[string]interface{}) + services[serviceName] = vals + parsedComposeFile["services"] = services + } + } + } + } + workingDir, err := filepath.Abs(filepath.Dir(composefilepath)) + if err != nil { + log.Errorf("Unable to compute full path of %s", composefilepath) + workingDir = filepath.Dir(composefilepath) + } + // Config details + configDetails := types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: []types.ConfigFile{{ + Filename: composefilepath, + Config: parsedComposeFile, + }}, + Environment: c.buildEnvironment(), + } + log.Debugf("About to load docker compose configuration") + config, err := loader.Load(configDetails) + if err != nil { + log.Warnf("Error while loading docker compose config : %s", err) + return irtypes.IR{}, err + } + + log.Debugf("About to start loading docker compose to intermediate rep") + ir, err := c.convertToIR(workingDir, *config) + if err != nil { + return irtypes.IR{}, err + } + + return ir, nil +} + +func (c *V3Loader) convertToIR(filedir string, composeObject types.Config) (irtypes.IR, error) { + ir := irtypes.IR{ + Services: make(map[string]irtypes.Service), + } + + //Secret volumes translated to IR + ir.Storages = c.getSecretStorages(composeObject.Secrets) + + //ConfigMap volumes translated to IR + ir.Storages = append(ir.Storages, c.getConfigStorages(composeObject.Configs)...) + + for _, composeServiceConfig := range composeObject.Services { + name := common.NormalizeForServiceName(composeServiceConfig.Name) + serviceConfig := irtypes.Service{Name: name} + serviceContainer := corev1.Container{} + + serviceContainer.Image = composeServiceConfig.Image + if serviceContainer.Image == "" { + serviceContainer.Image = name + ":latest" + } + if composeServiceConfig.Build.Dockerfile != "" && composeServiceConfig.Build.Context != "" { + //TODO: Add support for args and labels + c := containerizer.ReuseDockerfileContainerizer{} + con, err := c.GetContainer(filedir, name, serviceContainer.Image, composeServiceConfig.Build.Dockerfile, composeServiceConfig.Build.Context) + if err != nil { + log.Warnf("Unable to get containization script even though build parameters are present : %s", err) + } else { + ir.AddContainer(con) + } + } + serviceContainer.WorkingDir = composeServiceConfig.WorkingDir + serviceContainer.Command = composeServiceConfig.Entrypoint + serviceContainer.Args = composeServiceConfig.Command + serviceContainer.Stdin = composeServiceConfig.StdinOpen + serviceContainer.Name = strings.ToLower(composeServiceConfig.ContainerName) + if serviceContainer.Name == "" { + serviceContainer.Name = strings.ToLower(serviceConfig.Name) + } + serviceContainer.TTY = composeServiceConfig.Tty + serviceContainer.Ports = c.getPorts(composeServiceConfig.Ports, composeServiceConfig.Expose) + + serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) + serviceConfig.Labels = common.MergeStringMaps(composeServiceConfig.Labels, composeServiceConfig.Deploy.Labels) + if composeServiceConfig.Hostname != "" { + serviceConfig.Hostname = composeServiceConfig.Hostname + } + if composeServiceConfig.DomainName != "" { + serviceConfig.Subdomain = composeServiceConfig.DomainName + } + if composeServiceConfig.Pid != "" { + if composeServiceConfig.Pid == "host" { + serviceConfig.HostPID = true + } else { + log.Warnf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, composeServiceConfig.Pid) + } + } + securityContext := &corev1.SecurityContext{} + if composeServiceConfig.Privileged { + securityContext.Privileged = &composeServiceConfig.Privileged + } + if composeServiceConfig.User != "" { + uid, err := strconv.ParseInt(composeServiceConfig.User, 10, 64) + if err != nil { + log.Warn("Ignoring user directive. User to be specified as a UID (numeric).") + } else { + securityContext.RunAsUser = &uid + } + } + capsAdd := []corev1.Capability{} + capsDrop := []corev1.Capability{} + for _, capAdd := range composeServiceConfig.CapAdd { + capsAdd = append(capsAdd, corev1.Capability(capAdd)) + } + for _, capDrop := range composeServiceConfig.CapDrop { + capsDrop = append(capsDrop, corev1.Capability(capDrop)) + } + //set capabilities if it is not empty + if len(capsAdd) > 0 || len(capsDrop) > 0 { + securityContext.Capabilities = &corev1.Capabilities{ + Add: capsAdd, + Drop: capsDrop, + } + } + // update template only if securityContext is not empty + if *securityContext != (corev1.SecurityContext{}) { + serviceContainer.SecurityContext = securityContext + } + podSecurityContext := &corev1.PodSecurityContext{} + if !reflect.DeepEqual(*podSecurityContext, corev1.PodSecurityContext{}) { + serviceConfig.SecurityContext = podSecurityContext + } + + if composeServiceConfig.Deploy.Mode == "global" { + serviceConfig.Daemon = true + } + + serviceConfig.Networks = c.getNetworks(composeServiceConfig, composeObject) + + if (composeServiceConfig.Deploy.Resources != types.Resources{}) { + if composeServiceConfig.Deploy.Resources.Limits != nil { + resourceLimit := corev1.ResourceList{} + memLimit := libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Limits.MemoryBytes) + if memLimit != 0 { + resourceLimit[corev1.ResourceMemory] = *resource.NewQuantity(int64(memLimit), "RandomStringForFormat") + } + if composeServiceConfig.Deploy.Resources.Limits.NanoCPUs != "" { + cpuLimit, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Limits.NanoCPUs, 64) + if err != nil { + log.Warnf("Unable to convert cpu limits resources value : %s", err) + } + CPULimit := int64(cpuLimit * 1000) + if CPULimit != 0 { + resourceLimit[corev1.ResourceCPU] = *resource.NewMilliQuantity(CPULimit, resource.DecimalSI) + } + } + serviceContainer.Resources.Limits = resourceLimit + } + if composeServiceConfig.Deploy.Resources.Reservations != nil { + resourceRequests := corev1.ResourceList{} + MemReservation := libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Reservations.MemoryBytes) + if MemReservation != 0 { + resourceRequests[corev1.ResourceMemory] = *resource.NewQuantity(int64(MemReservation), "RandomStringForFormat") + } + if composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs != "" { + cpuReservation, err := strconv.ParseFloat(composeServiceConfig.Deploy.Resources.Reservations.NanoCPUs, 64) + if err != nil { + log.Warnf("Unable to convert cpu limits reservation value : %s", err) + } + CPUReservation := int64(cpuReservation * 1000) + if CPUReservation != 0 { + resourceRequests[corev1.ResourceCPU] = *resource.NewMilliQuantity(CPUReservation, resource.DecimalSI) + } + } + serviceContainer.Resources.Requests = resourceRequests + } + } + + // HealthCheck + if composeServiceConfig.HealthCheck != nil && !composeServiceConfig.HealthCheck.Disable { + probe, err := c.getHealthCheck(*composeServiceConfig.HealthCheck) + if err != nil { + log.Warnf("Unable to parse health check : %s", err) + } else { + serviceContainer.LivenessProbe = &probe + } + } + restart := composeServiceConfig.Restart + if composeServiceConfig.Deploy.RestartPolicy != nil { + restart = composeServiceConfig.Deploy.RestartPolicy.Condition + } + if restart == "unless-stopped" { + log.Warnf("Restart policy 'unless-stopped' in service %s is not supported, convert it to 'always'", name) + serviceConfig.RestartPolicy = corev1.RestartPolicyAlways + } + // replicas: + if composeServiceConfig.Deploy.Replicas != nil { + serviceConfig.Replicas = int(*composeServiceConfig.Deploy.Replicas) + } + serviceContainer.Env = c.getEnvs(composeServiceConfig) + + vml, vl := makeVolumesFromTmpFS(name, composeServiceConfig.Tmpfs) + for _, v := range vl { + serviceConfig.AddVolume(v) + } + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, vml...) + + for _, secret := range composeServiceConfig.Secrets { + target := filepath.Join(defaultSecretBasePath, secret.Source) + src := secret.Source + if secret.Target != "" { + tokens := strings.Split(secret.Source, "/") + var prefix string + if !strings.HasPrefix(secret.Target, "/") { + prefix = defaultSecretBasePath + "/" + } + if tokens[len(tokens)-1] == secret.Target { + target = prefix + secret.Source + } else { + target = prefix + strings.TrimSuffix(secret.Target, "/"+tokens[len(tokens)-1]) + } + src = tokens[len(tokens)-1] + } + + vSrc := corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secret.Source, + Items: []corev1.KeyToPath{{ + Key: secret.Source, + Path: src, + }}, + }, + } + + if secret.Mode != nil { + mode := cast.ToInt32(*secret.Mode) + vSrc.Secret.DefaultMode = &mode + } + + serviceConfig.AddVolume(corev1.Volume{ + Name: secret.Source, + VolumeSource: vSrc, + }) + + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, corev1.VolumeMount{ + Name: secret.Source, + MountPath: target, + }) + } + + for _, c := range composeServiceConfig.Configs { + target := c.Target + if target == "" { + target = "/" + c.Source + } + vSrc := corev1.ConfigMapVolumeSource{} + vSrc.Name = common.MakeFileNameCompliant(c.Source) + if o, ok := composeObject.Configs[c.Source]; ok { + if o.External.External { + log.Errorf("Config metadata %s has an external source", c.Source) + } else { + srcBaseName := filepath.Base(o.File) + vSrc.Items = []corev1.KeyToPath{{Key: srcBaseName, Path: filepath.Base(target)}} + if c.Mode != nil { + signedMode := int32(*c.Mode) + vSrc.DefaultMode = &signedMode + } + } + } else { + log.Errorf("Unable to find configmap object for %s", vSrc.Name) + } + serviceConfig.AddVolume(corev1.Volume{ + Name: vSrc.Name, + VolumeSource: corev1.VolumeSource{ConfigMap: &vSrc}, + }) + + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, + corev1.VolumeMount{ + Name: vSrc.Name, + MountPath: target, + SubPath: filepath.Base(target), + }) + } + + for index, vol := range composeServiceConfig.Volumes { + if isPath(vol.Source) { + volumeName := fmt.Sprintf("%s%d", common.VolumePrefix, index) + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: vol.Target, + }) + + serviceConfig.AddVolume(corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{Path: vol.Source}, + }, + }) + } else { + serviceContainer.VolumeMounts = append(serviceContainer.VolumeMounts, corev1.VolumeMount{ + Name: vol.Source, + MountPath: vol.Target, + }) + + serviceConfig.AddVolume(corev1.Volume{ + Name: vol.Source, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: vol.Source, + }, + }, + }) + storageObj := irtypes.Storage{StorageType: irtypes.PVCKind, Name: vol.Source, Content: nil} + ir.AddStorage(storageObj) + } + } + + serviceConfig.Containers = []corev1.Container{serviceContainer} + ir.Services[name] = serviceConfig + } + + return ir, nil +} + +func (c *V3Loader) buildEnvironment() map[string]string { + result := make(map[string]string) + //TODO: Check if any variable is mandatory and fill it with dummy value + if !common.IgnoreEnvironment { + env := os.Environ() + for _, s := range env { + if !strings.Contains(s, "=") { + log.Debugf("unexpected environment %q", s) + continue + } + kv := strings.SplitN(s, "=", 2) + result[kv[0]] = kv[1] + } + } + return result +} + +func (c *V3Loader) getSecretStorages(secrets map[string]types.SecretConfig) []irtypes.Storage { + storages := make([]irtypes.Storage, len(secrets)) + for secretName, secretObj := range secrets { + storage := irtypes.Storage{ + Name: secretName, + StorageType: irtypes.SecretKind, + } + + if !secretObj.External.External { + content, err := ioutil.ReadFile(secretObj.File) + if err != nil { + log.Warnf("Could not read the secret file [%s]", secretObj.File) + } else { + storage.Content = map[string][]byte{secretName: content} + } + } + + storages = append(storages, storage) + } + + return storages +} + +func (c *V3Loader) getConfigStorages(configs map[string]types.ConfigObjConfig) []irtypes.Storage { + Storages := make([]irtypes.Storage, len(configs)) + + for cfgName, cfgObj := range configs { + storage := irtypes.Storage{ + Name: cfgName, + StorageType: irtypes.ConfigMapKind, + } + + if !cfgObj.External.External { + fileInfo, err := os.Stat(cfgObj.File) + if err != nil { + log.Warnf("Could not identify the type of secret artifact [%s]. Encountered [%s]", cfgObj.File, err) + } else { + if !fileInfo.IsDir() { + content, err := ioutil.ReadFile(cfgObj.File) + if err != nil { + log.Warnf("Could not read the secret file [%s]. Encountered [%s]", cfgObj.File, err) + } else { + storage.Content = map[string][]byte{cfgName: content} + } + } else { + dataMap, err := c.getAllDirContentAsMap(cfgObj.File) + if err != nil { + log.Warnf("Could not read the secret directory [%s]. Encountered [%s]", cfgObj.File, err) + } else { + storage.Content = dataMap + } + } + } + } + Storages = append(Storages, storage) + } + + return Storages +} + +func (c *V3Loader) getPorts(ports []types.ServicePortConfig, expose []string) []corev1.ContainerPort { + containerPorts := []corev1.ContainerPort{} + exist := map[string]bool{} + for _, port := range ports { + proto := corev1.ProtocolTCP + if strings.EqualFold(string(corev1.ProtocolUDP), port.Protocol) { + proto = corev1.ProtocolUDP + } + containerPorts = append(containerPorts, corev1.ContainerPort{ + ContainerPort: int32(port.Target), + Protocol: proto, + }) + exist[cast.ToString(port.Target)] = true + } + for _, port := range expose { + portValue := port + protocol := corev1.ProtocolTCP + if strings.Contains(portValue, "/") { + splits := strings.Split(port, "/") + portValue = splits[0] + protocol = corev1.Protocol(strings.ToUpper(splits[1])) + } + if exist[portValue] { + continue + } + containerPorts = append(containerPorts, corev1.ContainerPort{ + ContainerPort: cast.ToInt32(portValue), + Protocol: protocol, + }) + } + + return containerPorts +} + +func (c *V3Loader) getNetworks(composeServiceConfig types.ServiceConfig, composeObject types.Config) (networks []string) { + networks = []string{} + for key := range composeServiceConfig.Networks { + netName := composeObject.Networks[key].Name + if netName == "" { + netName = key + } + networks = append(networks, netName) + } + return networks +} + +func (c *V3Loader) getHealthCheck(composeHealthCheck types.HealthCheckConfig) (corev1.Probe, error) { + probe := corev1.Probe{} + + if len(composeHealthCheck.Test) > 1 { + probe.Handler = corev1.Handler{ + Exec: &corev1.ExecAction{ + // docker/cli adds "CMD-SHELL" to the struct, hence we remove the first element of composeHealthCheck.Test + Command: composeHealthCheck.Test[1:], + }, + } + } else { + log.Warnf("Could not find command to execute in probe : %s", composeHealthCheck.Test) + } + if composeHealthCheck.Timeout != nil { + parse, err := time.ParseDuration(composeHealthCheck.Timeout.String()) + if err != nil { + return probe, errors.Wrap(err, "unable to parse health check timeout variable") + } + probe.TimeoutSeconds = int32(parse.Seconds()) + } + if composeHealthCheck.Interval != nil { + parse, err := time.ParseDuration(composeHealthCheck.Interval.String()) + if err != nil { + return probe, errors.Wrap(err, "unable to parse health check interval variable") + } + probe.PeriodSeconds = int32(parse.Seconds()) + } + if composeHealthCheck.Retries != nil { + probe.FailureThreshold = int32(*composeHealthCheck.Retries) + } + if composeHealthCheck.StartPeriod != nil { + parse, err := time.ParseDuration(composeHealthCheck.StartPeriod.String()) + if err != nil { + return probe, errors.Wrap(err, "unable to parse health check startPeriod variable") + } + probe.InitialDelaySeconds = int32(parse.Seconds()) + } + + return probe, nil +} + +func (c *V3Loader) getEnvs(composeServiceConfig types.ServiceConfig) (envs []corev1.EnvVar) { + for name, value := range composeServiceConfig.Environment { + var env corev1.EnvVar + if value != nil { + env = corev1.EnvVar{Name: name, Value: *value} + } else { + env = corev1.EnvVar{Name: name, Value: "unknown"} + } + envs = append(envs, env) + } + return envs +} + +func (c *V3Loader) getAllDirContentAsMap(directoryPath string) (map[string][]byte, error) { + fileList, err := ioutil.ReadDir(directoryPath) + if err != nil { + return nil, err + } + dataMap := make(map[string][]byte) + count := 0 + for _, file := range fileList { + if file.IsDir() { + continue + } + fileName := file.Name() + log.Debugf("Reading file into the data map: [%s]", fileName) + data, err := ioutil.ReadFile(filepath.Join(directoryPath, fileName)) + if err != nil { + log.Debugf("Unable to read file data : %s", fileName) + continue + } + dataMap[fileName] = data + count = count + 1 + } + log.Debugf("Read %d files into the data map", count) + return dataMap, nil +} diff --git a/internal/source/compose2kube.go b/internal/source/compose2kube.go new file mode 100644 index 000000000..f8e730bff --- /dev/null +++ b/internal/source/compose2kube.go @@ -0,0 +1,148 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + log "github.com/sirupsen/logrus" + + sourcetypes "github.com/konveyor/move2kube/internal/collector/sourcetypes" + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/source/compose" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// ComposeTranslator implements Translator interface +type ComposeTranslator struct { +} + +// GetTranslatorType returns the translator type +func (c ComposeTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.Compose2KubeTranslation +} + +// GetServiceOptions returns the service options for inputPath +func (c ComposeTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + servicesMap := make(map[string]plantypes.Service) + + //Load images + yamlpaths, err := common.GetFilesByExt(inputPath, []string{".yaml", ".yml"}) + if err != nil { + log.Warnf("Unable to fetch yaml files and recognize compose yamls : %s", err) + } + imagemetadatapaths := make(map[string]string) + for _, path := range yamlpaths { + im := new(collecttypes.ImageInfo) + if common.ReadYaml(path, &im) == nil && im.Kind == string(collecttypes.ImageMetadataKind) { + for _, imagetag := range im.Spec.Tags { + imagemetadatapaths[imagetag] = path + } + } + } + + //Fill data into plan + for _, path := range yamlpaths { + dc := new(sourcetypes.DockerCompose) + if common.ReadYaml(path, &dc) == nil { + path, _ = plan.GetRelativePath(path) + for serviceName, dcservice := range dc.DCServices { + log.Debugf("Found a Docker compose service : %s", serviceName) + serviceName = common.NormalizeForServiceName(serviceName) + if _, ok := servicesMap[serviceName]; !ok { + servicesMap[serviceName] = c.newService(serviceName) + } + service := servicesMap[serviceName] + service.Image = dcservice.Image + service.UpdateContainerBuildPipeline = false + service.UpdateDeployPipeline = true + service.AddSourceArtifact(plantypes.ComposeFileArtifactType, path) + if imagepath, ok := imagemetadatapaths[dcservice.Image]; ok { + imagepath, _ = plan.GetRelativePath(imagepath) + service.AddSourceArtifact(plantypes.ImageInfoArtifactType, imagepath) + } + servicesMap[serviceName] = service + } + } + } + + services := make([]plantypes.Service, len(servicesMap)) + i := 0 + for _, service := range servicesMap { + services[i] = service + i++ + } + return services, nil +} + +type composeIRTranslator interface { + ConvertToIR(filepath string, serviceName string) (irtypes.IR, error) +} + +// Translate translates the service to IR +func (c ComposeTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + + for _, service := range services { + if service.TranslationType == c.GetTranslatorType() { + for _, path := range service.SourceArtifacts[plantypes.ComposeFileArtifactType] { + fullpath := p.GetFullPath(path) + log.Debugf("File %s being loaded from compose service : %s", fullpath, service.ServiceName) + var dcfile sourcetypes.DockerCompose + err := common.ReadYaml(fullpath, &dcfile) + if err != nil { + log.Errorf("Unable to read docker compose yaml %s for version : %s", path, err) + } + log.Debugf("Docker Compose version: %s", dcfile.Version) + var t composeIRTranslator + switch dcfile.Version { + case "", "1", "1.0", "2", "2.0", "2.1": + t = new(compose.V1V2Loader) + case "3", "3.0", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8": + t = new(compose.V3Loader) + default: + log.Errorf("Version %s of Docker Compose is not supported (%s). Please use version 1, 2 or 3.", dcfile.Version, fullpath) + } + cir, err := t.ConvertToIR(fullpath, service.ServiceName) + if err != nil { + log.Errorf("Unable to parse docker compose file %s using %T : %s", fullpath, t, err) + } else { + ir.Merge(cir) + } + log.Debugf("Services returned by compose translator : %d", len(ir.Services)) + } + for _, path := range service.SourceArtifacts[plantypes.ImageInfoArtifactType] { + var imgMD collecttypes.ImageInfo + err := common.ReadYaml(p.GetFullPath(path), &imgMD) + if err != nil { + log.Errorf("Unable to read image yaml %s : %s", path, err) + } else { + ir.AddContainer(irtypes.NewContainerFromImageInfo(imgMD)) + } + } + } + } + + return ir, nil +} + +func (c ComposeTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.AddSourceType(plantypes.ComposeSourceTypeValue) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue //TODO: Identify when to use enhance + return service +} diff --git a/internal/source/data/Cfbuildpacks.yaml b/internal/source/data/Cfbuildpacks.yaml new file mode 100644 index 000000000..b3adc026e --- /dev/null +++ b/internal/source/data/Cfbuildpacks.yaml @@ -0,0 +1,18 @@ +kind: cfcontainerizers +buildpackcontainerizers: + - buildpackname: binary_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: java_buildpack_offline + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: hwc_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: dotnet_core_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 diff --git a/internal/source/data/constants.go b/internal/source/data/constants.go new file mode 100644 index 000000000..e9591aa60 --- /dev/null +++ b/internal/source/data/constants.go @@ -0,0 +1,45 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2020-09-14 15:52:48.575322 +0530 IST m=+0.000974414 + +/* +Copyright IBM Corporation 2020 + +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 data + +const ( + + Cfbuildpacks_yaml = `kind: cfcontainerizers +buildpackcontainerizers: + - buildpackname: binary_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: java_buildpack_offline + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: hwc_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 + - buildpackname: dotnet_core_buildpack + containerbuildtype: cnb + targetoptions: + - cloudfoundry/cnb:cflinuxfs3 +` + +) \ No newline at end of file diff --git a/internal/source/dockerfile2kube.go b/internal/source/dockerfile2kube.go new file mode 100644 index 000000000..a87d7bbbf --- /dev/null +++ b/internal/source/dockerfile2kube.go @@ -0,0 +1,324 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" + + git "github.com/go-git/go-git/v5" + dockerparser "github.com/moby/buildkit/frontend/dockerfile/parser" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/containerizer" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// DockerfileTranslator implements Translator interface for using preexisting dockerfiles +type DockerfileTranslator struct { +} + +// GetTranslatorType returns translator type +func (c DockerfileTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.Dockerfile2KubeTranslation +} + +// GetServiceOptions - output a plan based on the input directory contents +func (c DockerfileTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + services := make([]plantypes.Service, 0) + sdfs, err := getDockerfileServices(inputPath, plan.Name) + if err != nil { + log.Errorf("Unable to get Dockerfiles : %s", err) + } else { + for sn, dfs := range sdfs { + ns := c.newService(sn) + ns.Image = sn + ":latest" + ns.ContainerizationTargetOptions = []string{} + relpath, err := plan.GetRelativePath(dfs[0].context) + if err != nil { + log.Warnf("Unable to get relative path of context %s : %s", dfs[0].context, err) + } + ns.AddBuildArtifact(plantypes.SourceDirectoryBuildArtifactType, relpath) + for _, df := range dfs { + p, err := plan.GetRelativePath(df.path) + if err != nil { + log.Warnf("Unable to get relative path of context %s : %s", df.path, err) + } + ns.AddSourceArtifact(plantypes.DockerfileArtifactType, p) + ns.ContainerizationTargetOptions = append(ns.ContainerizationTargetOptions, p) + } + services = append(services, ns) + } + } + return services, err +} + +// Translate translates artifacts to IR +func (c DockerfileTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + ir := irtypes.NewIR(p) + for _, service := range services { + if service.TranslationType == c.GetTranslatorType() { + log.Debugf("Translating %s", service.ServiceName) + if len(service.ContainerizationTargetOptions) == 0 { + log.Warnf("No target options for service %s. Ignoring service.", service.ServiceName) + continue + } + serviceConfig := irtypes.Service{Name: service.ServiceName} + c := containerizer.ReuseDockerfileContainerizer{} + dfp := p.GetFullPath(service.ContainerizationTargetOptions[0]) + fp := filepath.Dir(dfp) + context := "." + if sc, ok := service.BuildArtifacts[plantypes.SourceDirectoryBuildArtifactType]; ok { + if len(sc) > 0 { + curcontext, err := filepath.Rel(fp, p.GetFullPath(sc[0])) + if err == nil { + context = curcontext + } + } + } + con, err := c.GetContainer(fp, service.ServiceName, service.Image, filepath.Base(dfp), context) + if err != nil { + log.Warnf("Unable to get containization script even though build parameters are present : %s", err) + } else { + ir.AddContainer(con) + serviceContainer := corev1.Container{Name: service.ServiceName} + serviceContainer.Image = service.Image + serviceConfig.Containers = []corev1.Container{serviceContainer} + ir.Services[service.ServiceName] = serviceConfig + } + } + } + return ir, nil +} + +func (c DockerfileTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.AddSourceType(plantypes.DirectorySourceTypeValue) + service.UpdateContainerBuildPipeline = true + service.UpdateDeployPipeline = true + return service +} + +func isDockerFile(path string) (isDockerfile bool, err error) { + f, err := os.Open(path) + if err != nil { + log.Debugf("Unable to open file %s : %s", path, err) + return false, err + } + defer f.Close() + res, err := dockerparser.Parse(f) + if err != nil { + log.Debugf("Unable to parse file %s as Docker files : %s", path, err) + return false, err + } + for _, dfchild := range res.AST.Children { + if dfchild.Value == "from" { + r := regexp.MustCompile(`(?i)FROM\s+(--platform=[^\s]+)?[^\s]+(\s+AS\s+[^\s]+)?\s*(#.+)?$`) + if r.MatchString(dfchild.Original) { + log.Debugf("Identified a docker file : " + path) + return true, nil + } + return false, nil + } + if dfchild.Value == "arg" { + continue + } + return false, fmt.Errorf("%s is not a valid Dockerfile", path) + } + return false, fmt.Errorf("%s is not a valid Dockerfile", path) +} + +func getGitRepoName(path string) (repo string, root string) { + r, err := git.PlainOpenWithOptions(filepath.Dir(path), &git.PlainOpenOptions{ + DetectDotGit: true, + }) + if err != nil { + log.Debugf("Unable to open %s as a git repo : %s", path, err) + return "", "" + } + remote, err := r.Remote("origin") + if err != nil { + log.Debugf("Unable to get origin remote : %s", err) + return "", "" + } + if len(remote.Config().URLs) == 0 { + log.Debugf("Unable to get origins") + return "", "" + } + u := remote.Config().URLs[0] + if strings.HasPrefix(u, "git") { + parts := strings.Split(u, ":") + if len(parts) != 2 { + // Unable to find git repo name + return "", "" + } + u = parts[1] + } + giturl, err := url.Parse(u) + if err != nil { + log.Debugf("Unable to get origin remote host : %s", err) + return "", "" + } + name := filepath.Base(giturl.Path) + name = strings.TrimSuffix(name, filepath.Ext(name)) + w, err := r.Worktree() + if err != nil { + log.Warnf("Unable to get root of repo : %s", err) + } + return name, w.Filesystem.Root() +} + +func getDockerfileServices(inputpath string, projName string) (sDockerfiles map[string][]dockerfile, err error) { + if info, err := os.Stat(inputpath); os.IsNotExist(err) { + log.Warnf("Error in walking through files due to : %s", err) + return sDockerfiles, err + } else if !info.IsDir() { + log.Warnf("The path %q is not a directory.", inputpath) + } + files := []string{} + err = filepath.Walk(inputpath, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("Skipping path %s due to error: %s", path, err) + return nil + } + // Skip directories + if info.IsDir() { + return nil + } + if isdf, _ := isDockerFile(path); isdf { + files = append(files, path) + } + return nil + }) + if err != nil { + log.Warnf("Error in walking through files due to : %s", err) + } + log.Debugf("No of dockerfiles identified : %d", len(files)) + repoDockerfiles := make(map[string][]dockerfile) + for _, f := range files { + repo, context := getGitRepoName(f) + if repo == "" { + repo = projName + context = inputpath + } + df := dockerfile{f, f, context} + if dfs, ok := repoDockerfiles[repo]; !ok { + repoDockerfiles[repo] = []dockerfile{df} + } else { + repoDockerfiles[repo] = append(dfs, df) + } + } + sDockerfiles = make(map[string][]dockerfile) + for repo, dfs := range repoDockerfiles { + if len(dfs) == 1 { + sDockerfiles[repo] = []dockerfile{dfs[0]} + continue + } + for k, v := range bucketDFs(dfs) { + separator := "-" + if repo == "" || k == "" { + separator = "" + } + nk := repo + separator + k + if v1, ok := sDockerfiles[nk]; ok { + sDockerfiles[nk] = append(v, v1...) + } else { + sDockerfiles[nk] = v + } + } + } + return sDockerfiles, nil +} + +type dockerfile struct { + path string + pathsuffix string + context string +} + +func bucketDFs(dfs []dockerfile) map[string][]dockerfile { + nDockerfiles := make(map[string][]dockerfile) + commonPath := findCommonPrefix(dfs) + if commonPath != "." { + dfs = trimPrefix(dfs, commonPath) + } + for _, df := range dfs { + parts := strings.Split(df.pathsuffix, string(filepath.Separator)) + prefix := "" + if len(parts) == 1 { + prefix = "" + } else if len(parts) > 1 { + prefix = parts[0] + df.context = strings.TrimSuffix(df.path, df.pathsuffix) + parts[0] + } + if pdfs, ok := nDockerfiles[prefix]; !ok { + nDockerfiles[prefix] = []dockerfile{df} + } else { + nDockerfiles[prefix] = append(pdfs, df) + } + } + sDockerfiles := make(map[string][]dockerfile) + for p, dfiles := range nDockerfiles { + if len(dfiles) == 1 { + sDockerfiles[p] = []dockerfile{dfiles[0]} + } else if p == "" { + for _, v := range dfiles { + if v1, ok := sDockerfiles[p]; ok { + sDockerfiles[p] = append(v1, v) + } else { + sDockerfiles[p] = []dockerfile{v} + } + } + } else { + for k, v := range bucketDFs(dfiles) { + separator := "-" + if p == "" || k == "" { + separator = "" + } + nk := p + separator + k + if v1, ok := sDockerfiles[nk]; ok { + sDockerfiles[nk] = append(v, v1...) + } else { + sDockerfiles[nk] = v + } + } + } + } + return sDockerfiles +} + +func findCommonPrefix(files []dockerfile) string { + paths := make([]string, len(files)) + for i, file := range files { + paths[i] = file.pathsuffix + } + return common.CleanAndFindCommonDirectory(paths) +} + +func trimPrefix(files []dockerfile, prefix string) []dockerfile { + for i, f := range files { + files[i].pathsuffix = strings.TrimPrefix(f.pathsuffix, prefix+string(filepath.Separator)) + } + return files +} diff --git a/internal/source/knative2kube.go b/internal/source/knative2kube.go new file mode 100644 index 000000000..7b1e303bf --- /dev/null +++ b/internal/source/knative2kube.go @@ -0,0 +1,48 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + "github.com/konveyor/move2kube/internal/apiresourceset" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// KnativeTranslator implements Translator interface +type KnativeTranslator struct { +} + +// GetTranslatorType returns the translator type +func (c KnativeTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.Knative2KubeTranslation +} + +// GetServiceOptions returns the service options for the inputPath +func (c KnativeTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + return (&apiresourceset.KnativeAPIResourceSet{}).GetServiceOptions(inputPath, plan) +} + +// Translate returns the IR for the plan service +func (c KnativeTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + return (&apiresourceset.KnativeAPIResourceSet{}).Translate(services, p) +} + +func (c KnativeTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + return service +} diff --git a/internal/source/kube2kube.go b/internal/source/kube2kube.go new file mode 100644 index 000000000..b46e295f6 --- /dev/null +++ b/internal/source/kube2kube.go @@ -0,0 +1,48 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + "github.com/konveyor/move2kube/internal/apiresourceset" + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// KubeTranslator implements Translator interface +type KubeTranslator struct { +} + +// GetTranslatorType returns the translator type +func (c KubeTranslator) GetTranslatorType() plantypes.TranslationTypeValue { + return plantypes.Kube2KubeTranslation +} + +// GetServiceOptions returns the possible service options for the inputPath +func (c KubeTranslator) GetServiceOptions(inputPath string, plan plantypes.Plan) ([]plantypes.Service, error) { + return (&apiresourceset.K8sAPIResourceSet{}).GetServiceOptions(inputPath, plan) +} + +// Translate returns the IR representation for the plan service +func (c KubeTranslator) Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) { + return (&apiresourceset.K8sAPIResourceSet{}).Translate(services, p) +} + +func (c KubeTranslator) newService(serviceName string) plantypes.Service { + service := plantypes.NewService(serviceName, c.GetTranslatorType()) + service.ContainerBuildType = plantypes.ReuseContainerBuildTypeValue + return service +} diff --git a/internal/source/testdata/datafortestingtranslate/expectedirfornodejsapp.yaml b/internal/source/testdata/datafortestingtranslate/expectedirfornodejsapp.yaml new file mode 100644 index 000000000..1673c4518 --- /dev/null +++ b/internal/source/testdata/datafortestingtranslate/expectedirfornodejsapp.yaml @@ -0,0 +1,106 @@ +name: myproject +services: + nodejs: + podspec: + volumes: [] + initcontainers: [] + containers: + - name: nodejs + image: nodejs:latest + command: [] + args: [] + workingdir: "" + ports: [] + envfrom: [] + env: [] + resources: + limits: {} + requests: {} + volumemounts: [] + volumedevices: [] + livenessprobe: null + readinessprobe: null + startupprobe: null + lifecycle: null + terminationmessagepath: "" + terminationmessagepolicy: "" + imagepullpolicy: "" + securitycontext: null + stdin: false + stdinonce: false + tty: false + ephemeralcontainers: [] + restartpolicy: "" + terminationgraceperiodseconds: null + activedeadlineseconds: null + dnspolicy: "" + nodeselector: {} + serviceaccountname: "" + deprecatedserviceaccount: "" + automountserviceaccounttoken: null + nodename: "" + hostnetwork: false + hostpid: false + hostipc: false + shareprocessnamespace: null + securitycontext: null + imagepullsecrets: [] + hostname: "" + subdomain: "" + affinity: null + schedulername: "" + tolerations: [] + hostaliases: [] + priorityclassname: "" + priority: null + dnsconfig: null + readinessgates: [] + runtimeclassname: null + enableservicelinks: null + preemptionpolicy: null + overhead: {} + topologyspreadconstraints: [] + name: nodejs + annotations: {} + labels: {} + replicas: 0 + networks: [] + exposeservice: false + daemon: false +storages: [] +containers: + - imagenames: + - nodejs:latest + new: true + newfiles: + nodejscnbbuilder.sh: |- + # Copyright IBM Corporation 2020 + # + # 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. + + pack build nodejs:latest -B cloudfoundry/cnb:cflinuxfs3 + exposedports: + - 8080 + userid: -1 + accesseddirs: [] +kubernetes: + artifactType: Yamls + clusterType: Kubernetes +targetclusterspec: + storageClasses: [] + apiKindVersionMap: {} +cachedobjects: [] +values: + registryurl: "" + registrynamespace: "" + services: {} diff --git a/internal/source/testdata/datafortestingtranslate/servicesfromnodejsapp.yaml b/internal/source/testdata/datafortestingtranslate/servicesfromnodejsapp.yaml new file mode 100644 index 000000000..9d6b9653b --- /dev/null +++ b/internal/source/testdata/datafortestingtranslate/servicesfromnodejsapp.yaml @@ -0,0 +1,34 @@ +--- +- serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: CNB + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true +- serviceName: svc1 + image: svc1:latest + translationType: Compose2Kube + containerBuildType: NewDockerfile + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - svc1src + buildArtifacts: + SourceCode: + - svc1src + updateContainerBuildPipeline: true + updateDeployPipeline: true +... \ No newline at end of file diff --git a/internal/source/testdata/expectedservicesforjavamavenappwithm2kignorecase2.yaml b/internal/source/testdata/expectedservicesforjavamavenappwithm2kignorecase2.yaml new file mode 100644 index 000000000..23942bbab --- /dev/null +++ b/internal/source/testdata/expectedservicesforjavamavenappwithm2kignorecase2.yaml @@ -0,0 +1,18 @@ +--- +- serviceName: java-maven + image: java-maven:latest + translationType: Any2Kube + containerBuildType: CNB + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - includeme/includeme/java-maven + buildArtifacts: + SourceCode: + - includeme/includeme/java-maven + updateContainerBuildPipeline: true + updateDeployPipeline: true +... \ No newline at end of file diff --git a/internal/source/testdata/expectedservicesfornodejsapp.yaml b/internal/source/testdata/expectedservicesfornodejsapp.yaml new file mode 100644 index 000000000..4118ad0be --- /dev/null +++ b/internal/source/testdata/expectedservicesfornodejsapp.yaml @@ -0,0 +1,18 @@ +--- +- serviceName: nodejs + image: nodejs:latest + translationType: Any2Kube + containerBuildType: CNB + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - . + buildArtifacts: + SourceCode: + - . + updateContainerBuildPipeline: true + updateDeployPipeline: true +... \ No newline at end of file diff --git a/internal/source/testdata/expectedservicesfornodejsappwithm2kignorecase1.yaml b/internal/source/testdata/expectedservicesfornodejsappwithm2kignorecase1.yaml new file mode 100644 index 000000000..97712dfad --- /dev/null +++ b/internal/source/testdata/expectedservicesfornodejsappwithm2kignorecase1.yaml @@ -0,0 +1,18 @@ +--- +- serviceName: includeme + image: includeme:latest + translationType: Any2Kube + containerBuildType: CNB + sourceType: + - Directory + targetOptions: + - cloudfoundry/cnb:cflinuxfs3 + sourceArtifacts: + SourceCode: + - excludeme/includeme + buildArtifacts: + SourceCode: + - excludeme/includeme + updateContainerBuildPipeline: true + updateDeployPipeline: true +... \ No newline at end of file diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/.m2kignore b/internal/source/testdata/javamavenappwithm2kignorecase2/.m2kignore new file mode 100644 index 000000000..23f658ae9 --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/.m2kignore @@ -0,0 +1,6 @@ +. +includeme/ +includeme/excludeme1/ +includeme/excludeme1/* +includeme/excludeme2/ +includeme/excludeme2/* diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme1/src/simplewebapp.php b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme1/src/simplewebapp.php new file mode 100644 index 000000000..15fe874fb --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme1/src/simplewebapp.php @@ -0,0 +1,20 @@ + + +This is a PHP App"; +?> diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/main.js b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/main.js new file mode 100644 index 000000000..b4559eaed --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/main.js @@ -0,0 +1,23 @@ +/* +Copyright IBM Corporation 2020 + +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. +*/ + +var serverObj = require('http'); +var PORT = 8080; + +serverObj.createServer(function (requestObj, responseObj) { + responseObj.write('

This is a node server

'); + responseObj.end(); +}).listen(PORT); diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/package.json b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/package.json new file mode 100644 index 000000000..20471ca81 --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/excludeme2/package.json @@ -0,0 +1,9 @@ +{ + "name" : "main", + "version" : "0.1.0", + "description": "This is a test web server", + "keywords": ["test", "webserver"], + "scripts": { + "start": "node main.js" + } +} diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/pom.xml b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/pom.xml new file mode 100644 index 000000000..ec00416c6 --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + com.simplewebapp + simplewebapp + war + 1.0 + simplewebapp Maven Webapp + + simplewebapp + + diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/WEB-INF/web.xml b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..9259e3a4c --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + + + + Maven Web App For Move2Kube + diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/index.jsp b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/index.jsp new file mode 100644 index 000000000..6e7d8b44a --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/includeme/includeme/java-maven/src/main/webapp/index.jsp @@ -0,0 +1,22 @@ + + + + +

This is a java-maven web app

+ + diff --git a/internal/source/testdata/javamavenappwithm2kignorecase2/package.json b/internal/source/testdata/javamavenappwithm2kignorecase2/package.json new file mode 100644 index 000000000..08008c957 --- /dev/null +++ b/internal/source/testdata/javamavenappwithm2kignorecase2/package.json @@ -0,0 +1 @@ +this is an " invalid json file for testing diff --git a/internal/source/testdata/m2kignoreforignorecontents b/internal/source/testdata/m2kignoreforignorecontents new file mode 100644 index 000000000..26e4ffa55 --- /dev/null +++ b/internal/source/testdata/m2kignoreforignorecontents @@ -0,0 +1,2 @@ +. +includeme/* diff --git a/internal/source/testdata/nodejsappwithm2kignorecase1/.m2kignore b/internal/source/testdata/nodejsappwithm2kignorecase1/.m2kignore new file mode 100644 index 000000000..50aca6249 --- /dev/null +++ b/internal/source/testdata/nodejsappwithm2kignorecase1/.m2kignore @@ -0,0 +1 @@ +/excludeme/ diff --git a/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/main.js b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/main.js new file mode 100644 index 000000000..b4559eaed --- /dev/null +++ b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/main.js @@ -0,0 +1,23 @@ +/* +Copyright IBM Corporation 2020 + +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. +*/ + +var serverObj = require('http'); +var PORT = 8080; + +serverObj.createServer(function (requestObj, responseObj) { + responseObj.write('

This is a node server

'); + responseObj.end(); +}).listen(PORT); diff --git a/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/package.json b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/package.json new file mode 100644 index 000000000..20471ca81 --- /dev/null +++ b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/includeme/package.json @@ -0,0 +1,9 @@ +{ + "name" : "main", + "version" : "0.1.0", + "description": "This is a test web server", + "keywords": ["test", "webserver"], + "scripts": { + "start": "node main.js" + } +} diff --git a/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/main.js b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/main.js new file mode 100644 index 000000000..b4559eaed --- /dev/null +++ b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/main.js @@ -0,0 +1,23 @@ +/* +Copyright IBM Corporation 2020 + +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. +*/ + +var serverObj = require('http'); +var PORT = 8080; + +serverObj.createServer(function (requestObj, responseObj) { + responseObj.write('

This is a node server

'); + responseObj.end(); +}).listen(PORT); diff --git a/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/package.json b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/package.json new file mode 100644 index 000000000..20471ca81 --- /dev/null +++ b/internal/source/testdata/nodejsappwithm2kignorecase1/excludeme/package.json @@ -0,0 +1,9 @@ +{ + "name" : "main", + "version" : "0.1.0", + "description": "This is a test web server", + "keywords": ["test", "webserver"], + "scripts": { + "start": "node main.js" + } +} diff --git a/internal/source/testdata/testmultiplem2kignores.tar b/internal/source/testdata/testmultiplem2kignores.tar new file mode 100644 index 000000000..5cb3b6867 Binary files /dev/null and b/internal/source/testdata/testmultiplem2kignores.tar differ diff --git a/internal/source/translator.go b/internal/source/translator.go new file mode 100644 index 000000000..4cc0b8ee8 --- /dev/null +++ b/internal/source/translator.go @@ -0,0 +1,72 @@ +/* +Copyright IBM Corporation 2020 + +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 source + +import ( + log "github.com/sirupsen/logrus" + + irtypes "github.com/konveyor/move2kube/internal/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// Translator interface defines loader that translates files and converts it to ir representation +type Translator interface { + GetTranslatorType() plantypes.TranslationTypeValue + GetServiceOptions(inputPath string, p plantypes.Plan) ([]plantypes.Service, error) + Translate(services []plantypes.Service, p plantypes.Plan) (irtypes.IR, error) + newService(serviceName string) plantypes.Service +} + +// GetSourceLoaders returns loader for given format +func GetSourceLoaders() []Translator { + var l = []Translator{new(DockerfileTranslator), new(ComposeTranslator), new(CfManifestTranslator), new(KnativeTranslator), new(KubeTranslator), new(Any2KubeTranslator)} //Any2Kube should be the last option + // new(ImagesTranslator)} + return l +} + +// Translate loads all sources +func Translate(p plantypes.Plan) (irtypes.IR, error) { + ts := GetSourceLoaders() + ir := irtypes.NewIR(p) + log.Infoln("Begin Translation") + for _, l := range ts { + log.Infof("[%T] Begin translation", l) + validservices := []plantypes.Service{} + for _, services := range p.Spec.Inputs.Services { + //Choose the first service even if there are multiple options + service := services[0] + if service.TranslationType == l.GetTranslatorType() { + validservices = append(validservices, service) + } + } + log.Debugf("Services to translate : %d", len(validservices)) + currir, err := l.Translate(validservices, p) + log.Debugf("Services translated : %d", len(currir.Services)) + log.Debugf("Containers translated : %d", len(currir.Containers)) + if err != nil { + log.Warnf("[%T] Failed : %s", l, err.Error()) + } else { + log.Infof("[%T] Done", l) + } + ir.Merge(currir) + log.Debugf("Total Services after translation : %d", len(ir.Services)) + log.Debugf("Total Containers after translation : %d", len(ir.Containers)) + } + log.Infoln("Translation done") + + return ir, nil +} diff --git a/internal/transformer/composetransformer.go b/internal/transformer/composetransformer.go new file mode 100644 index 000000000..537965930 --- /dev/null +++ b/internal/transformer/composetransformer.go @@ -0,0 +1,101 @@ +/* +Copyright IBM Corporation 2020 + +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 transform + +import ( + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + + composetypes "github.com/docker/cli/cli/compose/types" + + "github.com/konveyor/move2kube/internal/common" + irtypes "github.com/konveyor/move2kube/internal/types" +) + +// ComposeTransformer implements Transformer interface +type ComposeTransformer struct { + Compose composeConfig + Containers []irtypes.Container + Name string +} + +type composeConfig struct { + Version string + Services map[string]composetypes.ServiceConfig `yaml:",omitempty"` + Networks map[string]composetypes.NetworkConfig `yaml:",omitempty"` + Volumes map[string]composetypes.VolumeConfig `yaml:",omitempty"` + Secrets map[string]composetypes.SecretConfig `yaml:",omitempty"` + Configs map[string]composetypes.ConfigObjConfig `yaml:",omitempty"` +} + +// Transform translates intermediate representation to destination objects +func (kt *ComposeTransformer) Transform(ir irtypes.IR) error { + log.Debugf("Starting Compose transform") + log.Debugf("Total services to be transformed : %d", len(ir.Services)) + + kt.Name = ir.Name + kt.Containers = ir.Containers + kt.Compose = composeConfig{ + Version: "3.5", + Services: make(map[string]composetypes.ServiceConfig), + } + + var exposedPort uint32 = 8080 + for _, service := range ir.Services { + for _, container := range service.Containers { + ports := make([]composetypes.ServicePortConfig, 0) + for _, port := range container.Ports { + ports = append(ports, composetypes.ServicePortConfig{ + Target: uint32(port.ContainerPort), + Published: exposedPort, + }) + exposedPort++ + } + env := make(composetypes.MappingWithEquals) + for _, e := range container.Env { + env[e.Name] = &e.Value + } + serviceConfig := composetypes.ServiceConfig{ + ContainerName: container.Name, + Image: container.Image, + Ports: ports, + Environment: env, + } + kt.Compose.Services[service.Name] = serviceConfig + //TODO: Support more than one container + } + } + log.Debugf("Total transformed objects : %d", len(kt.Compose.Services)) + + return nil +} + +// WriteObjects writes Transformed objects to filesystem +func (kt *ComposeTransformer) WriteObjects(outpath string) error { + err := os.MkdirAll(outpath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create output directory %s : %s", outpath, err) + } + artifactspath := filepath.Join(outpath, "docker-compose.yaml") + err = common.WriteYaml(artifactspath, kt.Compose) + if err != nil { + log.Errorf("Unable to write docker compose file %s : %s", artifactspath, err) + } + return nil +} diff --git a/internal/transformer/k8stransformer.go b/internal/transformer/k8stransformer.go new file mode 100644 index 000000000..7cb961378 --- /dev/null +++ b/internal/transformer/k8stransformer.go @@ -0,0 +1,228 @@ +/* +Copyright IBM Corporation 2020 + +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 transform + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + knativev1 "knative.dev/serving/pkg/apis/serving/v1" + + "github.com/konveyor/move2kube/internal/apiresourceset" + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/transformer/templates" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + outputtypes "github.com/konveyor/move2kube/types/output" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// K8sTransformer implements Transformer interface +type K8sTransformer struct { + TransformedObjects []runtime.Object + Containers []irtypes.Container + Values outputtypes.HelmValues + TargetClusterSpec collecttypes.ClusterMetadataSpec + Helm bool + Name string + IgnoreUnsupportedKinds bool +} + +// Transform translates intermediate representation to destination objects +func (kt *K8sTransformer) Transform(ir irtypes.IR) error { + log.Debugf("Starting Kubernetes transform") + log.Debugf("Total services to be transformed : %d", len(ir.Services)) + + kt.Name = ir.Name + kt.Values = ir.Values + kt.Containers = ir.Containers + kt.TargetClusterSpec = ir.TargetClusterSpec + kt.Helm = (ir.Kubernetes.ArtifactType != plantypes.Yamls) + + kt.TransformedObjects = (&apiresourceset.K8sAPIResourceSet{}).CreateAPIResources(ir) + + log.Debugf("Total transformed objects : %d", len(kt.TransformedObjects)) + + return nil +} + +// WriteObjects writes Transformed objects to filesystem +func (kt *K8sTransformer) WriteObjects(outpath string) error { + areNewImagesCreated := writeContainers(outpath, kt.Containers, kt.Values.RegistryURL, kt.Values.RegistryNamespace) + + artifactspath := filepath.Join(outpath, kt.Name) + if kt.Helm { + _ = kt.generateHelmMetadata(artifactspath, kt.Values) + artifactspath = filepath.Join(artifactspath, "templates") + } + log.Debugf("Total services to be serialized : %d", len(kt.TransformedObjects)) + + scheme := (&apiresourceset.K8sAPIResourceSet{}).GetScheme() + objs := []runtime.Object{} + for _, obj := range kt.TransformedObjects { + kind := obj.GetObjectKind().GroupVersionKind().Kind + versions := kt.TargetClusterSpec.GetSupportedVersions(kind) + version := obj.GetObjectKind().GroupVersionKind().String() + if versions == nil { + if kt.IgnoreUnsupportedKinds { + log.Errorf("Kind %s unsupported in target cluster. Will ignore object. %+v", kind, obj.GetObjectKind()) + continue + } + } else { + if kind == "Service" { + for _, v := range versions { + if !strings.HasPrefix(v, knativev1.SchemeGroupVersion.Group) { + version = v + } + } + } else { + version = versions[0] + } + } + groupversion, err := schema.ParseGroupVersion(version) + if err != nil { + log.Errorf("Unable to parse group version %s : %s", version, err) + continue + } + //Change to supported version + newobj, err := scheme.ConvertToVersion(obj, schema.GroupVersion{Group: groupversion.Group, Version: groupversion.Version}) + if err != nil { + log.Errorf("Error while transforming version : %s. Writing in original version.", err) + //continue + } else { + obj = newobj + } + objs = append(objs, obj) + } + + _, err := writeTransformedObjects(artifactspath, objs) + if err != nil { + log.Errorf("Error occured while writing transformed objects %s", err) + } + if kt.Helm { + _ = kt.createOperator(kt.Name, outpath) + } else { + kt.writeDeployScript(kt.Name, outpath) + } + kt.writeReadMe(kt.Name, areNewImagesCreated, kt.Helm, outpath) + return nil +} + +func (kt *K8sTransformer) generateHelmMetadata(dirName string, values outputtypes.HelmValues) error { + err := os.MkdirAll(dirName, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create Helm Metadata directory %s : %s", dirName, err) + return err + } + //README.md + readme := "This chart was created by Move2Kube\n" + err = ioutil.WriteFile(filepath.Join(dirName, "README.md"), []byte(readme), common.DefaultFilePermission) + if err != nil { + log.Errorf("Error while writing Readme : %s", err) + } + + //Chart.yaml + type ChartDetails struct { + Name string + } + err = common.WriteTemplateToFile(templates.Chart_tpl, ChartDetails{filepath.Base(dirName)}, filepath.Join(dirName, "Chart.yaml"), common.DefaultFilePermission) + if err != nil { + log.Errorf("Error while writing Chart.yaml : %s", err) + } + + //values.yaml + outputPath := filepath.Join(dirName, "values.yaml") + err = common.WriteYaml(outputPath, values) + if err != nil { + log.Warn("Error in writing Helm values", err) + } else { + log.Debugf("Wrote Helm values to file: %s", outputPath) + } + + err = common.WriteTemplateToFile(templates.Helminstall_sh, struct { + Project string + }{ + Project: filepath.Base(dirName), + }, filepath.Join(filepath.Dir(dirName), "helminstall.sh"), common.DefaultExecutablePermission) + return err +} + +func (kt *K8sTransformer) createOperator(projectname string, basepath string) error { + _, err := exec.LookPath("operator-sdk") + if err != nil { + log.Warnf("Unable to find operator-sdk. Skipping operator generation : %s", err) + return err + } + operatorname := projectname + "-operator" + operatorpath := filepath.Join(basepath, operatorname) + _, err = os.Stat(operatorpath) + if !os.IsNotExist(err) { + os.RemoveAll(operatorpath) + } + err = os.MkdirAll(operatorpath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create Operator directory %s : %s", operatorpath, err) + return err + } + helmchartpath, err := filepath.Abs(filepath.Join(basepath, projectname)) + if err != nil { + log.Warnf("Could not resolve helm chart path : %s", err) + return err + } + cmd := exec.Command("operator-sdk", "init", "--plugins=helm", "--helm-chart="+helmchartpath, "--domain=io", "--group="+projectname, "--version=v1alpha1") + cmd.Dir = operatorpath + output, err := cmd.Output() + if err != nil { + log.Warnf("Error during operator creation : %s, %s", err, output) + return err + } + return nil +} + +func (kt *K8sTransformer) writeDeployScript(proj string, outpath string) { + err := common.WriteTemplateToFile(templates.Deploy_sh, struct { + Project string + }{ + Project: proj, + }, filepath.Join(outpath, "deploy.sh"), common.DefaultExecutablePermission) + if err != nil { + log.Errorf("Unable to write deploy script : %s", err) + } +} + +func (kt *K8sTransformer) writeReadMe(project string, areNewImages bool, isHelm bool, outpath string) { + err := common.WriteTemplateToFile(templates.K8sReadme_md, struct { + Project string + NewImages bool + Helm bool + }{ + Project: project, + NewImages: areNewImages, + Helm: isHelm, + }, filepath.Join(outpath, "Readme.md"), common.DefaultFilePermission) + if err != nil { + log.Errorf("Unable to write readme : %s", err) + } +} diff --git a/internal/transformer/knativetransformer.go b/internal/transformer/knativetransformer.go new file mode 100644 index 000000000..1576a99cf --- /dev/null +++ b/internal/transformer/knativetransformer.go @@ -0,0 +1,99 @@ +/* +Copyright IBM Corporation 2020 + +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 transform + +import ( + "path/filepath" + + log "github.com/sirupsen/logrus" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/konveyor/move2kube/internal/apiresourceset" + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/transformer/templates" + irtypes "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + outputtypes "github.com/konveyor/move2kube/types/output" +) + +// KnativeTransformer implements Transformer interface +type KnativeTransformer struct { + TransformedObjects []runtime.Object + Containers []irtypes.Container + Values outputtypes.HelmValues + TargetClusterSpec collecttypes.ClusterMetadataSpec + Name string + IgnoreUnsupportedKinds bool +} + +// Transform translates intermediate representation to destination objects +func (kt *KnativeTransformer) Transform(ir irtypes.IR) error { + log.Debugf("Starting Knative transform") + log.Debugf("Total services to be transformed : %d", len(ir.Services)) + + kt.Name = ir.Name + kt.Values = ir.Values + kt.Containers = ir.Containers + kt.TargetClusterSpec = ir.TargetClusterSpec + kt.IgnoreUnsupportedKinds = ir.Kubernetes.IgnoreUnsupportedKinds + kt.TransformedObjects = (&apiresourceset.KnativeAPIResourceSet{}).CreateAPIResources(ir) + + log.Debugf("Total transformed objects : %d", len(kt.TransformedObjects)) + + return nil +} + +// WriteObjects writes Transformed objects to filesystem +func (kt *KnativeTransformer) WriteObjects(outpath string) error { + areNewImagesCreated := writeContainers(outpath, kt.Containers, kt.Values.RegistryURL, kt.Values.RegistryNamespace) + + artifactspath := filepath.Join(outpath, kt.Name) + log.Debugf("Total services to be serialized : %d", len(kt.TransformedObjects)) + + _, err := writeTransformedObjects(artifactspath, kt.TransformedObjects) + if err != nil { + log.Errorf("Error occured while writing transformed objects %s", err) + } + kt.writeDeployScript(kt.Name, outpath) + kt.writeReadeMe(kt.Name, areNewImagesCreated, outpath) + return nil +} + +func (kt *KnativeTransformer) writeDeployScript(proj string, outpath string) { + err := common.WriteTemplateToFile(templates.Deploy_sh, struct { + Project string + }{ + Project: proj, + }, filepath.Join(outpath, "deploy.sh"), common.DefaultExecutablePermission) + if err != nil { + log.Errorf("Unable to write deploy script : %s", err) + } +} + +func (kt *KnativeTransformer) writeReadeMe(project string, areNewImages bool, outpath string) { + err := common.WriteTemplateToFile(templates.KnativeReadme_md, struct { + Project string + NewImages bool + }{ + Project: project, + NewImages: areNewImages, + }, filepath.Join(outpath, "Readme.md"), common.DefaultFilePermission) + if err != nil { + log.Errorf("Unable to write Readme : %s", err) + } +} diff --git a/internal/transformer/templates/Buildimages.sh b/internal/transformer/templates/Buildimages.sh new file mode 100644 index 000000000..1a306aaf8 --- /dev/null +++ b/internal/transformer/templates/Buildimages.sh @@ -0,0 +1,18 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +{{range $key, $val := .}} +cd {{$val}} +./{{$key}} +cd -{{end}} diff --git a/internal/transformer/templates/Chart.tpl b/internal/transformer/templates/Chart.tpl new file mode 100644 index 000000000..73a5e2b8e --- /dev/null +++ b/internal/transformer/templates/Chart.tpl @@ -0,0 +1,8 @@ +name: {{.Name}} +description: A generated Helm Chart for {{.Name}} +version: 0.1.0 +apiVersion: v1 +keywords: + - {{.Name}} +sources: +home: \ No newline at end of file diff --git a/internal/transformer/templates/CopySources.sh b/internal/transformer/templates/CopySources.sh new file mode 100644 index 000000000..7818cdaf6 --- /dev/null +++ b/internal/transformer/templates/CopySources.sh @@ -0,0 +1,14 @@ +# Copyright IBM Corporation 2020 +# +# 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. +cp -r {{.Src}}/* {{.Dst}}/ diff --git a/internal/transformer/templates/Deploy.sh b/internal/transformer/templates/Deploy.sh new file mode 100644 index 000000000..3d7a94633 --- /dev/null +++ b/internal/transformer/templates/Deploy.sh @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +kubectl apply -f {{ .Project }} \ No newline at end of file diff --git a/internal/transformer/templates/Helminstall.sh b/internal/transformer/templates/Helminstall.sh new file mode 100644 index 000000000..b74b19bfe --- /dev/null +++ b/internal/transformer/templates/Helminstall.sh @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +helm upgrade -i {{ .Project }} {{ .Project }} \ No newline at end of file diff --git a/internal/transformer/templates/K8sReadme.md b/internal/transformer/templates/K8sReadme.md new file mode 100644 index 000000000..cece702f0 --- /dev/null +++ b/internal/transformer/templates/K8sReadme.md @@ -0,0 +1,21 @@ +Move2Kube +--------- +Congratulations! Move2Kube has generated the necessary build artfiacts for moving all your application components to Kubernetes. Using the artifacts in this directory you can deploy your application in a kubernetes cluster. + +Prerequisites +------------- +* Docker +* Helm +* Kubectl +* Source-To-Image (S2I) https://github.com/openshift/source-to-image + +Next Steps +---------- +{{if .NewImages -}} +* Copy the source directory into the containers folder for packaging as containers using "copysource.sh " +* Build your images using "buildimages.sh" +* Push images to registry "pushimages.sh " +{{end -}} +{{- if .Helm -}}* Your helm chart is at {{ .Project }}, you can install it using "helminstall.sh" or you can use the operator.{{- else -}} +* Use "deploy.sh" to deploy your artifacts into a kubernetes cluster. +{{- end}} diff --git a/internal/transformer/templates/KnativeReadme.md b/internal/transformer/templates/KnativeReadme.md new file mode 100644 index 000000000..c2f22e2af --- /dev/null +++ b/internal/transformer/templates/KnativeReadme.md @@ -0,0 +1,17 @@ +Move2Kube +--------- +Congratulations! Move2Kube has generated the necessary build artfiacts for moving all your application components to Knative. Using the artifacts in this directory you can deploy your application in a Knative instance + +Prerequisites +------------- +* Docker +* Kubectl + +Next Steps +---------- +{{if .NewImages -}} +* Copy this directory into your base source directory, so that the scripts gets merged at the right contexts. +* Build your images using buildimages.sh +* Push images to registry pushimages.sh +{{end -}} +* Use deploy.sh to deploy your artifacts into a knative. \ No newline at end of file diff --git a/internal/transformer/templates/Manualimages.md b/internal/transformer/templates/Manualimages.md new file mode 100644 index 000000000..fc993a42f --- /dev/null +++ b/internal/transformer/templates/Manualimages.md @@ -0,0 +1,6 @@ +Manual containers +----------------- +There is no known automated containerization approach for the below container requirements. + +{{range $image := .Images}}{{$image}} +{{end}} \ No newline at end of file diff --git a/internal/transformer/templates/Pushimages.sh b/internal/transformer/templates/Pushimages.sh new file mode 100644 index 000000000..c9af5e26d --- /dev/null +++ b/internal/transformer/templates/Pushimages.sh @@ -0,0 +1,30 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +# Invoke as pushimages.sh + +if [ "$#" -ne 2 ]; then + REGISTRY_URL={{ .RegistryURL }} + REGISTRY_NAMESPACE={{ .RegistryNamespace }} +else + REGISTRY_URL=$1 + REGISTRY_NAMESPACE=$2 +fi + +# Uncomment the below line if you want to enable login before pushing +# docker login ${REGISTRY_URL} + +{{range $image := .Images}}docker tag {{$image}} ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/{{$image}} +docker push ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/{{$image}} +{{end}} \ No newline at end of file diff --git a/internal/transformer/templates/constants.go b/internal/transformer/templates/constants.go new file mode 100644 index 000000000..178a80097 --- /dev/null +++ b/internal/transformer/templates/constants.go @@ -0,0 +1,181 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2020-09-14 15:52:49.237152 +0530 IST m=+0.001411422 + +/* +Copyright IBM Corporation 2020 + +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 templates + +const ( + + Buildimages_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +{{range $key, $val := .}} +cd {{$val}} +./{{$key}} +cd -{{end}} +` + + Chart_tpl = `name: {{.Name}} +description: A generated Helm Chart for {{.Name}} +version: 0.1.0 +apiVersion: v1 +keywords: + - {{.Name}} +sources: +home:` + + CopySources_sh = `# Copyright IBM Corporation 2020 +# +# 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. +cp -r {{.Src}}/* {{.Dst}}/ +` + + Deploy_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +kubectl apply -f {{ .Project }}` + + Helminstall_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +helm upgrade -i {{ .Project }} {{ .Project }}` + + K8sReadme_md = `Move2Kube +--------- +Congratulations! Move2Kube has generated the necessary build artfiacts for moving all your application components to Kubernetes. Using the artifacts in this directory you can deploy your application in a kubernetes cluster. + +Prerequisites +------------- +* Docker +* Helm +* Kubectl +* Source-To-Image (S2I) https://github.com/openshift/source-to-image + +Next Steps +---------- +{{if .NewImages -}} +* Copy the source directory into the containers folder for packaging as containers using "copysource.sh " +* Build your images using "buildimages.sh" +* Push images to registry "pushimages.sh " +{{end -}} +{{- if .Helm -}}* Your helm chart is at {{ .Project }}, you can install it using "helminstall.sh" or you can use the operator.{{- else -}} +* Use "deploy.sh" to deploy your artifacts into a kubernetes cluster. +{{- end}} +` + + KnativeReadme_md = `Move2Kube +--------- +Congratulations! Move2Kube has generated the necessary build artfiacts for moving all your application components to Knative. Using the artifacts in this directory you can deploy your application in a Knative instance + +Prerequisites +------------- +* Docker +* Kubectl + +Next Steps +---------- +{{if .NewImages -}} +* Copy this directory into your base source directory, so that the scripts gets merged at the right contexts. +* Build your images using buildimages.sh +* Push images to registry pushimages.sh +{{end -}} +* Use deploy.sh to deploy your artifacts into a knative.` + + Manualimages_md = `Manual containers +----------------- +There is no known automated containerization approach for the below container requirements. + +{{range $image := .Images}}{{$image}} +{{end}}` + + Pushimages_sh = `# Copyright IBM Corporation 2020 +# +# 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. + +# Invoke as pushimages.sh + +if [ "$#" -ne 2 ]; then + REGISTRY_URL={{ .RegistryURL }} + REGISTRY_NAMESPACE={{ .RegistryNamespace }} +else + REGISTRY_URL=$1 + REGISTRY_NAMESPACE=$2 +fi + +# Uncomment the below line if you want to enable login before pushing +# docker login ${REGISTRY_URL} + +{{range $image := .Images}}docker tag {{$image}} ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/{{$image}} +docker push ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/{{$image}} +{{end}}` + +) \ No newline at end of file diff --git a/internal/transformer/transformer.go b/internal/transformer/transformer.go new file mode 100644 index 000000000..b157134ed --- /dev/null +++ b/internal/transformer/transformer.go @@ -0,0 +1,203 @@ +/* +Copyright IBM Corporation 2020 + +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 transform + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "strings" + + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/internal/transformer/templates" + irtypes "github.com/konveyor/move2kube/internal/types" + "github.com/konveyor/move2kube/types/plan" +) + +//go:generate go run github.com/konveyor/move2kube/internal/common/generator templates + +// Transformer translates intermediate representation to destination artifacts +type Transformer interface { + // Transform translates intermediate representation to destination objects + Transform(ir irtypes.IR) error + // WriteObjects writes Transformed objects to filesystem + WriteObjects(outDirectory string) error +} + +// GetTransformer returns a transformer that is suitable for an IR +func GetTransformer(ir irtypes.IR) Transformer { + if ir.Kubernetes.ArtifactType == plan.Knative { + return &KnativeTransformer{} + } + return &K8sTransformer{} +} + +func writeContainers(outpath string, containers []irtypes.Container, registryURL string, registryNamespace string) bool { //Returns if any scripts were written + containersdirectory := "containers" + containerspath := path.Join(outpath, containersdirectory) + log.Debugf("containerspath %s", containerspath) + err := os.MkdirAll(containerspath, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create directory %s : %s", containerspath, err) + } + log.Debugf("Total number of containers : %d", len(containers)) + buildscripts := []string{} + dockerimages := []string{} + manualimages := []string{} + for _, container := range containers { + log.Debugf("Container : %t", container.New) + if !container.New { + continue + } + if len(container.NewFiles) == 0 { + manualimages = append(manualimages, container.ImageNames...) + } + log.Debugf("New Container : %s", container.ImageNames[0]) + dockerimages = append(dockerimages, container.ImageNames...) + for path, filecontents := range container.NewFiles { + writepath := filepath.Join(containerspath, path) + directory := filepath.Dir(writepath) + err := os.MkdirAll(directory, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create directory %s : %s", directory, err) + continue + } + var fileperm os.FileMode + if filepath.Ext(writepath) == ".sh" { + fileperm = common.DefaultExecutablePermission + buildscripts = append(buildscripts, filepath.Join(containersdirectory, path)) + } else { + fileperm = common.DefaultFilePermission + } + log.Debugf("Writing at %s", writepath) + err = ioutil.WriteFile(writepath, []byte(filecontents), fileperm) + if err != nil { + log.Warnf("Error writing file at %s : %s", writepath, err) + } + } + } + //Write build scripts + if len(manualimages) > 0 { + writepath := filepath.Join(outpath, "Manualimages.md") + err := common.WriteTemplateToFile(templates.Manualimages_md, struct { + Scripts []string + }{ + Scripts: manualimages, + }, writepath, common.DefaultFilePermission) + if err != nil { + log.Errorf("Unable to create manual image : %s", err) + } + } + if len(buildscripts) > 0 { + buildScriptMap := make(map[string]string) + for _, value := range buildscripts { + buildScriptDir, buildScriptFile := filepath.Split(value) + buildScriptMap[buildScriptFile] = buildScriptDir + } + log.Debugf("buildscripts %s", buildscripts) + log.Debugf("buildScriptMap %s", buildScriptMap) + writepath := filepath.Join(outpath, "buildimages.sh") + err := common.WriteTemplateToFile(templates.Buildimages_sh, buildScriptMap, writepath, common.DefaultExecutablePermission) + if err != nil { + log.Errorf("Unable to create script to build images : %s", err) + } + + writepath = filepath.Join(outpath, "copysources.sh") + err = common.WriteTemplateToFile(templates.CopySources_sh, struct { + Src string + Dst string + }{ + Src: "$1", //Parameterized source folder + Dst: containersdirectory, + }, writepath, common.DefaultExecutablePermission) + if err != nil { + log.Errorf("Unable to create script to build images : %s", err) + } + } + if len(dockerimages) > 0 { + writepath := filepath.Join(outpath, "pushimages.sh") + err := common.WriteTemplateToFile(templates.Pushimages_sh, struct { + Images []string + RegistryURL string + RegistryNamespace string + }{ + Images: dockerimages, + RegistryURL: registryURL, + RegistryNamespace: registryNamespace, + }, writepath, common.DefaultExecutablePermission) + if err != nil { + log.Errorf("Unable to create script to push images : %s", err) + } + return true + } + return false +} + +func writeTransformedObjects(path string, objs []runtime.Object) ([]string, error) { + fileswritten := []string{} + err := os.MkdirAll(path, common.DefaultDirectoryPermission) + if err != nil { + log.Errorf("Unable to create directory %s : %s", path, err) + return nil, err + } + for _, obj := range objs { + //Encode object + j, err := json.Marshal(obj) + if err != nil { + log.Errorf("Error while Marshalling object : %s", err) + continue + } + var jsonObj interface{} + err = yaml.Unmarshal(j, &jsonObj) + if err != nil { + log.Errorf("Unable to unmarshal json : %s", err) + continue + } + var b bytes.Buffer + encoder := yaml.NewEncoder(&b) + encoder.SetIndent(2) + if err := encoder.Encode(jsonObj); err != nil { + log.Errorf("Error while Encoding object : %s", err) + continue + } + //Write to file + data := b.Bytes() + val := reflect.ValueOf(obj).Elem() + typeMeta := val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta) + objectMeta := val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta) + file := fmt.Sprintf("%s-%s.yaml", objectMeta.Name, strings.ToLower(typeMeta.Kind)) + file = filepath.Join(path, file) + fileswritten = append(fileswritten, file) + if err := ioutil.WriteFile(file, []byte(data), common.DefaultFilePermission); err != nil { + log.Error("Failed to write %s: %s"+strings.ToLower(typeMeta.Kind), err) + continue + } + log.Debugf("%q created", file) + } + return fileswritten, nil +} diff --git a/internal/types/ir.go b/internal/types/ir.go new file mode 100644 index 000000000..b435e2078 --- /dev/null +++ b/internal/types/ir.go @@ -0,0 +1,298 @@ +/* +Copyright IBM Corporation 2020 + +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 types + +import ( + "strings" + + log "github.com/sirupsen/logrus" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + common "github.com/konveyor/move2kube/internal/common" + collecttypes "github.com/konveyor/move2kube/types/collection" + outputtypes "github.com/konveyor/move2kube/types/output" + "github.com/konveyor/move2kube/types/plan" +) + +// IR is the intermediate representation +type IR struct { + Name string + Services map[string]Service + Storages []Storage + Containers []Container + + Kubernetes plan.KubernetesOutput + + TargetClusterSpec collecttypes.ClusterMetadataSpec + CachedObjects []runtime.Object + + Values outputtypes.HelmValues +} + +// Service defines structure of an IR service +type Service struct { + corev1.PodSpec + + Name string + Annotations map[string]string + Labels map[string]string + Replicas int + Networks []string + ExposeService bool + Daemon bool //Gets converted to DaemonSet +} + +// AddVolume adds a volume to a service +func (service *Service) AddVolume(volume corev1.Volume) { + merged := false + for _, existingVolume := range service.Volumes { + if existingVolume.Name == volume.Name { + log.Debugf("Found an existing volume. Ignoring new volume : %+v", volume) + merged = true + break + } + } + if !merged { + service.Volumes = append(service.Volumes, volume) + } +} + +// Container defines structure of a container +type Container struct { + ImageNames []string + New bool + NewFiles map[string]string //[filename][filecontents] + ExposedPorts []int + UserID int + AccessedDirs []string +} + +// NewContainer creates a new container +func NewContainer(imagename string, new bool) Container { + return Container{ + ImageNames: []string{imagename}, + New: new, + NewFiles: make(map[string]string), + ExposedPorts: []int{}, + UserID: -1, + AccessedDirs: []string{}, + } +} + +// NewContainerFromImageInfo creates a new container from image info +func NewContainerFromImageInfo(i collecttypes.ImageInfo) Container { + imagename := "" + if len(i.Spec.Tags) > 0 { + imagename = i.Spec.Tags[0] + } else { + log.Errorf("The image info %v has no tags. Leaving the tag empty for the container.", i) + } + c := NewContainer(imagename, false) + c.ImageNames = i.Spec.Tags + c.ExposedPorts = i.Spec.PortsToExpose + c.UserID = i.Spec.UserID + c.AccessedDirs = i.Spec.AccessedDirs + return c +} + +// Merge merges containers +func (c *Container) Merge(newc Container) bool { + for _, imagename := range newc.ImageNames { + if common.IsStringPresent(c.ImageNames, imagename) { + if c.New != newc.New { + log.Errorf("Both old and new image seems to share the same tag for container %s.", imagename) + } else if c.New && newc.New { + for filepath, filecontents := range newc.NewFiles { + if contents, ok := c.NewFiles[filepath]; ok { + if contents != filecontents { + log.Errorf("Two build scripts found for image : %s in %s. Ignoring new script.", imagename, filepath) + } + } else { + c.NewFiles[filepath] = filecontents + } + } + if c.UserID != newc.UserID { + log.Errorf("Two different users found for image : %d in %d. Ignoring new users.", c.UserID, newc.UserID) + } + } + c.ImageNames = common.MergeStringSlices(c.ImageNames, newc.ImageNames) + c.ExposedPorts = common.MergeIntSlices(c.ExposedPorts, newc.ExposedPorts) + c.AccessedDirs = common.MergeStringSlices(c.AccessedDirs, newc.AccessedDirs) //Needs to be clarified + if !c.New { + c.NewFiles = newc.NewFiles + c.UserID = newc.UserID //Needs to be clarified + } + return true + } + log.Debugf("Mismatching during container merge [%s, %s]", c.ImageNames, imagename) + } + return false +} + +// AddFile adds a file to a container +func (c *Container) AddFile(path string, newcontents string) { + if contents, ok := c.NewFiles[path]; ok { + if contents != newcontents { + log.Errorf("Script already exists for image at %s. Ignoring new script.", path) + } + } else { + c.NewFiles[path] = newcontents + } +} + +// AddExposedPort adds an exposed port to a container +func (c *Container) AddExposedPort(port int) { + if !common.IsIntPresent(c.ExposedPorts, port) { + c.ExposedPorts = append(c.ExposedPorts, port) + } +} + +// AddImageName adds image name to a container +func (c *Container) AddImageName(imagename string) { + if !common.IsStringPresent(c.ImageNames, imagename) { + c.ImageNames = append(c.ImageNames, imagename) + } +} + +// AddAccessedDirs adds accessed directories to container +func (c *Container) AddAccessedDirs(dirname string) { + if !common.IsStringPresent(c.AccessedDirs, dirname) { + c.AccessedDirs = append(c.AccessedDirs, dirname) + } +} + +// NewIR creates a new IR +func NewIR(p plan.Plan) IR { + var ir IR + ir.Name = p.Name + ir.Kubernetes = p.Spec.Outputs.Kubernetes + ir.Containers = make([]Container, 0) + ir.Services = make(map[string]Service) + ir.Storages = make([]Storage, 0) + ir.TargetClusterSpec = collecttypes.ClusterMetadataSpec{ + StorageClasses: []string{}, + APIKindVersionMap: make(map[string][]string), + } + ir.Values.GlobalVariables = make(map[string]string) + return ir +} + +// Merge merges IRs +func (ir *IR) Merge(newir IR) { + if ir.Name != newir.Name { + if ir.Name == "" { + ir.Name = newir.Name + } + } + ir.Kubernetes.Merge(newir.Kubernetes) + for scname, sc := range newir.Services { + if _, ok := ir.Services[scname]; ok { + log.Warnf("Two services of same service name %s. Using the new object.", scname) + } + ir.Services[scname] = sc + } + for _, newcontainer := range newir.Containers { + ir.AddContainer(newcontainer) + } + for _, newst := range newir.Storages { + ir.AddStorage(newst) + } + ir.TargetClusterSpec.Merge(newir.TargetClusterSpec) + ir.CachedObjects = append(ir.CachedObjects, newir.CachedObjects...) + ir.Values.Merge(newir.Values) +} + +// StorageKindType defines storage type kind +type StorageKindType string + +const ( + // SecretKind defines storage type of Secret + SecretKind StorageKindType = "Secret" + // ConfigMapKind defines storage type of ConfigMap + ConfigMapKind StorageKindType = "ConfigMap" + // PVCKind defines storage type of PersistentVolumeClaim + PVCKind StorageKindType = "PersistentVolumeClaim" + // PullSecretKind defines storage type of pull secret + PullSecretKind StorageKindType = "PullSecret" +) + +// Storage defines structure of a storage +type Storage struct { + Name string + corev1.PersistentVolumeClaimSpec //This promotion contains the volumeName which is used by configmap, secrets and pvc. + StorageType StorageKindType //Type of storage cfgmap, secret, pvc + Content map[string][]byte //Optional field meant to store content for cfgmap or secret +} + +// Merge merges storage +func (s *Storage) Merge(newst Storage) bool { + if strings.Compare(s.Name, newst.Name) == 0 { + if s.Content != nil && newst.Content != nil { + s.Content = newst.Content + } + s.StorageType = newst.StorageType + s.PersistentVolumeClaimSpec = newst.PersistentVolumeClaimSpec + return true + } + log.Debugf("Mismatching storages [%s, %s]", s.Name, newst.Name) + return false +} + +// AddContainer adds a conatainer to IR +func (ir *IR) AddContainer(container Container) { + merged := false + for i := range ir.Containers { + if ir.Containers[i].Merge(container) { + merged = true + break + } + } + if !merged { + ir.Containers = append(ir.Containers, container) + } +} + +// AddStorage adds a storage to IR +func (ir *IR) AddStorage(st Storage) { + merged := false + for i := range ir.Storages { + if ir.Storages[i].Merge(st) { + merged = true + break + } + } + if !merged { + ir.Storages = append(ir.Storages, st) + } +} + +// GetContainer returns container which has the imagename +func (ir *IR) GetContainer(imagename string) (con Container, exists bool) { + for _, c := range ir.Containers { + if common.IsStringPresent(c.ImageNames, imagename) { + return c, true + } else if c.New { + parts := strings.Split(imagename, "/") + if len(parts) > 2 && parts[0] == ir.Kubernetes.RegistryURL && common.IsStringPresent(c.ImageNames, parts[len(parts)-1]) { + return c, true + } + } + } + return Container{}, false +} diff --git a/internal/types/ir_test.go b/internal/types/ir_test.go new file mode 100644 index 000000000..24a3b3cfb --- /dev/null +++ b/internal/types/ir_test.go @@ -0,0 +1,777 @@ +/* +Copyright IBM Corporation 2020 + +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 types_test + +import ( + "reflect" + "testing" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/types" + collecttypes "github.com/konveyor/move2kube/types/collection" + plantypes "github.com/konveyor/move2kube/types/plan" + corev1 "k8s.io/api/core/v1" +) + +func TestAddVolume(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a volume to an empty service", func(t *testing.T) { + // Setup + name1 := "name1" + v := corev1.Volume{Name: name1} + s := types.Service{} + want := types.Service{} + want.Volumes = []corev1.Volume{v} + + // Test + s.AddVolume(v) + if !reflect.DeepEqual(s, want) { + t.Fatal("Failed to add volume to an empty service properly. Expected:", want, "Actual:", s) + } + }) + + t.Run("add a new volume with same name to a filled service", func(t *testing.T) { + // Setup + name1 := "name1" + v := corev1.Volume{Name: name1} + s := types.Service{} + s.Volumes = []corev1.Volume{v} + want := types.Service{} + want.Volumes = []corev1.Volume{v} + + // Test + s.AddVolume(v) + if !reflect.DeepEqual(s, want) { + t.Fatal("Failed to add a volume with the same name to a filled service properly. Expected:", want, "Actual:", s) + } + }) +} + +func TestNewContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + name1 := "name1" + new1 := true + c := types.NewContainer(name1, new1) + if len(c.ImageNames) != 1 || c.ImageNames[0] != name1 { + t.Fatal("Failed to initialize the container properly. Expected image names to be: [", name1, "] Actual:", c.ImageNames) + } + if c.New != new1 { + t.Fatal("Failed to initialize the container properly. Expected New to be:", new1, "Actual:", c.New) + } + if c.NewFiles == nil { + t.Fatal("Failed to initialize the container properly. The map NewFiles is not initialized. Actual:", c.NewFiles) + } +} + +func TestNewContainerFromImageInfo(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container from image with tags", func(t *testing.T) { + imginfo1 := collecttypes.NewImageInfo() + imginfo1.Spec.Tags = []string{"tag1"} + c := types.NewContainerFromImageInfo(imginfo1) + if !reflect.DeepEqual(c.ImageNames, imginfo1.Spec.Tags) { + t.Fatal("Failed to initialze the image names from the tags properly. Expected image names:", imginfo1.Spec.Tags, "Actual:", c.ImageNames) + } + if !reflect.DeepEqual(c.ExposedPorts, imginfo1.Spec.PortsToExpose) { + t.Fatal("Failed to initialze the ports from the image info properly. Expected ports:", imginfo1.Spec.PortsToExpose, "Actual:", c.ExposedPorts) + } + if c.UserID != imginfo1.Spec.UserID { + t.Fatal("The user ids are not the same. Expected:", imginfo1.Spec.UserID, "Actual:", c.UserID) + } + if !reflect.DeepEqual(c.AccessedDirs, imginfo1.Spec.AccessedDirs) { + t.Fatal("Failed to initialze the directories from the image info properly. Expected ports:", imginfo1.Spec.AccessedDirs, "Actual:", c.AccessedDirs) + } + }) + + t.Run("get container from image without tags", func(t *testing.T) { + imginfo1 := collecttypes.NewImageInfo() + c := types.NewContainerFromImageInfo(imginfo1) + if !reflect.DeepEqual(c.ImageNames, imginfo1.Spec.Tags) { + t.Fatal("Failed to initialze the image names from the tags properly. Expected image names:", imginfo1.Spec.Tags, "Actual:", c.ImageNames) + } + if !reflect.DeepEqual(c.ExposedPorts, imginfo1.Spec.PortsToExpose) { + t.Fatal("Failed to initialze the ports from the image info properly. Expected ports:", imginfo1.Spec.PortsToExpose, "Actual:", c.ExposedPorts) + } + if c.UserID != imginfo1.Spec.UserID { + t.Fatal("The user ids are not the same. Expected:", imginfo1.Spec.UserID, "Actual:", c.UserID) + } + if !reflect.DeepEqual(c.AccessedDirs, imginfo1.Spec.AccessedDirs) { + t.Fatal("Failed to initialze the directories from the image info properly. Expected ports:", imginfo1.Spec.AccessedDirs, "Actual:", c.AccessedDirs) + } + }) +} + +func TestContainerMerge(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("merge 2 empty containers", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c1 := types.NewContainer(name1, new1) + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + want := types.NewContainer(name1, new1) + + // Test + if c1.Merge(c2) || !reflect.DeepEqual(c1, want) { // TODO: If neither container has image name should it return true? + t.Fatal("Failed to merge 2 empty containers properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge containers that share no image names", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname4", "imgname5", "imgname6"} + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + + // Test + if c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Should not merge 2 containers having no image names in common. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge containers that share some image names", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + name2 := "name2" + new2 := false + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge 2 containers having common image names properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge 2 new containers that share some image names and have the same build scripts", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1" + contents1 := "contents1" + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + c1.NewFiles[path1] = contents1 + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + c2.NewFiles[path1] = contents1 + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + want.NewFiles[path1] = contents1 + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge the 2 containers properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge 2 new containers that share some image names and having different build scripts", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1" + path2 := "path2" + contents1 := "contents1" + contents2 := "contents2" + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + c1.NewFiles[path1] = contents1 + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + c2.NewFiles[path2] = contents2 + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + want.NewFiles[path1] = contents1 + want.NewFiles[path2] = contents2 + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge the 2 containers properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge 2 new containers that share some image names and having different build scripts for the same key", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1" + contents1 := "contents1" + contents2 := "contents2" + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + c1.NewFiles[path1] = contents1 + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + c2.NewFiles[path1] = contents2 + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + want.NewFiles[path1] = contents1 + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge the 2 containers properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge 2 new containers that share some image names but have different user ids", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + c1.UserID = 1 + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + c2.UserID = 2 + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + want.UserID = 1 + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge the 2 containers properly. Expected:", want, "Actual:", c1) + } + }) + + t.Run("merge a new container into an old container that share some image names but have different user ids and build scripts", func(t *testing.T) { + // Setup + path1 := "path1" + path2 := "path2" + contents1 := "contents1" + contents2 := "contents2" + + name1 := "name1" + new1 := false + c1 := types.NewContainer(name1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + c1.UserID = 1 + c1.NewFiles[path1] = contents1 + + name2 := "name2" + new2 := true + c2 := types.NewContainer(name2, new2) + c2.ImageNames = []string{"imgname3", "imgname4", "imgname5"} + c2.UserID = 2 + c2.NewFiles[path2] = contents2 + + want := types.NewContainer(name1, new1) + want.ImageNames = []string{"imgname1", "imgname2", "imgname3", "imgname4", "imgname5"} + want.UserID = 2 + want.NewFiles[path2] = contents2 + + // Test + if !c1.Merge(c2) || !reflect.DeepEqual(c1, want) { + t.Fatal("Failed to merge the 2 containers properly. Expected:", want, "Actual:", c1) + } + }) +} + +func TestAddFile(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new script to an empty container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1/foo/bar" + contents1 := "contents1" + c := types.NewContainer(name1, new1) + want := types.NewContainer(name1, new1) + want.NewFiles[path1] = contents1 + + // Test + c.AddFile(path1, contents1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Failed to add the new script to the empty container properly. Expected:", want, "Actual:", c) + } + }) + + t.Run("add the same script at the same path to a filled container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1/foo/bar" + contents1 := "contents1" + c := types.NewContainer(name1, new1) + c.NewFiles[path1] = contents1 + want := types.NewContainer(name1, new1) + want.NewFiles[path1] = contents1 + + // Test + c.AddFile(path1, contents1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Adding the same script to the same path should not change the container. Expected:", want, "Actual:", c) + } + }) + + t.Run("add a different script at the same path to a filled container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + path1 := "path1/foo/bar" + contents1 := "contents1" + contents2 := "contents2" + c := types.NewContainer(name1, new1) + c.NewFiles[path1] = contents1 + want := types.NewContainer(name1, new1) + want.NewFiles[path1] = contents1 + + // Test + c.AddFile(path1, contents2) + if !reflect.DeepEqual(c, want) { + t.Fatal("Adding a different script to the same path should not change the container. Expected:", want, "Actual:", c) + } + }) +} + +func TestAddExposedPort(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new port to expose to an empty container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + port1 := 8080 + c := types.NewContainer(name1, new1) + want := types.NewContainer(name1, new1) + want.ExposedPorts = append(want.ExposedPorts, port1) + + // Test + c.AddExposedPort(port1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Failed to add the new port to the list of exposed ports properly. Expected:", want, "Actual:", c) + } + }) + + t.Run("add an already exposed port to a filled container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + port1 := 8080 + c := types.NewContainer(name1, new1) + c.ExposedPorts = append(c.ExposedPorts, port1) + want := types.NewContainer(name1, new1) + want.ExposedPorts = append(want.ExposedPorts, port1) + + // Test + c.AddExposedPort(port1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Adding an already exposed port should not change the container. Expected:", want, "Actual:", c) + } + }) +} + +func TestAddImageName(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new image name to an empty container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + img1 := "img1" + c := types.NewContainer(name1, new1) + want := types.NewContainer(name1, new1) + want.ImageNames = append(want.ImageNames, img1) + + // Test + c.AddImageName(img1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Failed to add an image name to an empty container properly. Expected:", want, "Actual:", c) + } + }) + + t.Run("add an existing image name to a filled container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + img1 := "img1" + c := types.NewContainer(name1, new1) + c.ImageNames = append(c.ImageNames, img1) + want := types.NewContainer(name1, new1) + want.ImageNames = append(want.ImageNames, img1) + + // Test + c.AddImageName(img1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Adding an existing image name should not change the container. Expected:", want, "Actual:", c) + } + }) +} + +func TestAddAccessedDirs(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new directory to an empty container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + dir1 := "dir1" + c := types.NewContainer(name1, new1) + want := types.NewContainer(name1, new1) + want.AccessedDirs = append(want.AccessedDirs, dir1) + + // Test + c.AddAccessedDirs(dir1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Failed to add a new directory to an empty container properly. Expected:", want, "Actual:", c) + } + }) + + t.Run("add an existing directory to a filled container", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + dir1 := "dir1" + c := types.NewContainer(name1, new1) + c.AccessedDirs = append(c.AccessedDirs, dir1) + want := types.NewContainer(name1, new1) + want.AccessedDirs = append(want.AccessedDirs, dir1) + + // Test + c.AddAccessedDirs(dir1) + if !reflect.DeepEqual(c, want) { + t.Fatal("Adding an existing directory should not change the container. Expected:", want, "Actual:", c) + } + }) +} + +func TestNewIR(t *testing.T) { + log.SetLevel(log.DebugLevel) + + p := plantypes.NewPlan() + ir := types.NewIR(p) + if ir.Containers == nil || + ir.Services == nil || + ir.Storages == nil || + ir.Values.GlobalVariables == nil { + t.Fatal("Failed to initialize the maps inside IR properly. The IR returned by NewIR:", ir) + } +} + +func TestIRMerge(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("merge 2 empty irs", func(t *testing.T) { + // Setup + p1 := plantypes.NewPlan() + ir1 := types.NewIR(p1) + p2 := plantypes.NewPlan() + ir2 := types.NewIR(p2) + p3 := plantypes.NewPlan() + want := types.NewIR(p3) + // Test + ir1.Merge(ir2) + if !reflect.DeepEqual(ir1, want) { + t.Fatal("Failed to merge the 2 irs properly. Expected:", want, "Actual:", ir1) + } + }) + + t.Run("merge 2 irs with different names", func(t *testing.T) { + // Setup + name1 := "name1" + name2 := "name2" + p1 := plantypes.NewPlan() + ir1 := types.NewIR(p1) + ir1.Name = name1 + p2 := plantypes.NewPlan() + ir2 := types.NewIR(p2) + ir2.Name = name2 + p3 := plantypes.NewPlan() + want := types.NewIR(p3) + want.Name = name1 + // Test + ir1.Merge(ir2) + if !reflect.DeepEqual(ir1, want) { + t.Fatal("Failed to merge the 2 irs properly. Expected:", want, "Actual:", ir1) + } + }) + + t.Run("merge an ir with a name into an ir with an empty name", func(t *testing.T) { + // Setup + name1 := "name1" + p1 := plantypes.NewPlan() + ir1 := types.NewIR(p1) + ir1.Name = "" + + p2 := plantypes.NewPlan() + ir2 := types.NewIR(p2) + ir2.Name = name1 + + p3 := plantypes.NewPlan() + want := types.NewIR(p3) + want.Name = name1 + + // Test + ir1.Merge(ir2) + if !reflect.DeepEqual(ir1, want) { + t.Fatal("Failed to merge the 2 irs properly. Expected:", want, "Actual:", ir1) + } + }) + + t.Run("merge 2 filled irs", func(t *testing.T) { + // Setup + contname1 := "contname1" + new1 := true + c1 := types.NewContainer(contname1, new1) + c1.ImageNames = []string{"imgname1", "imgname2", "imgname3"} + + s1 := types.Storage{Name: "storage1"} + + svcname1 := "svcname1" + svc1 := types.Service{Name: svcname1, Replicas: 2} + svc2 := types.Service{Name: svcname1, Replicas: 4} + + p1 := plantypes.NewPlan() + ir1 := types.NewIR(p1) + ir1.Services[svcname1] = svc1 + + p2 := plantypes.NewPlan() + ir2 := types.NewIR(p2) + ir2.Services[svcname1] = svc2 + ir2.Containers = append(ir2.Containers, c1) + ir2.Storages = append(ir2.Storages, s1) + + p3 := plantypes.NewPlan() + want := types.NewIR(p3) + want.Services[svcname1] = svc2 + want.Containers = append(want.Containers, c1) + want.Storages = append(want.Storages, s1) + + // Test + ir1.Merge(ir2) + if !reflect.DeepEqual(ir1, want) { + t.Fatal("Failed to merge the 2 irs properly. Expected:", want, "Actual:", ir1) + } + }) +} + +func TestStorageMerge(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("merge empty storage into empty storage", func(t *testing.T) { + s1 := types.Storage{} + s2 := types.Storage{} + want := types.Storage{} + if !s1.Merge(s2) || !reflect.DeepEqual(s1, want) { + t.Fatal("Failed to merge 2 empty storages properly. Expected:", want, "Actual:", s1) + } + }) + + t.Run("merge storages with different names", func(t *testing.T) { + s1 := types.Storage{} + s1.Name = "name1" + s2 := types.Storage{} + s2.Name = "name2" + want := types.Storage{} + want.Name = "name1" + if s1.Merge(s2) || !reflect.DeepEqual(s1, want) { + t.Fatal("Should not merge 2 storages with different names. Merge should return false. Expected:", want, "Actual:", s1) + } + }) + + t.Run("merge filled storage into filled storage", func(t *testing.T) { + // Setup + s1 := types.Storage{} + s1.Content = map[string][]byte{"key1": []byte("val1")} + s2 := types.Storage{} + s2.Content = map[string][]byte{"key2": []byte("val2")} + want := types.Storage{} + want.Content = map[string][]byte{"key2": []byte("val2")} + // Test + if !s1.Merge(s2) || !reflect.DeepEqual(s1, want) { + t.Fatal("Failed to merge 2 filled storages properly. Expected:", want, "Actual:", s1) + } + }) +} + +func TestAddContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new container to an empty IR", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c := types.NewContainer(name1, new1) + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + p2 := plantypes.NewPlan() + want := types.NewIR(p2) + want.Containers = append(want.Containers, c) + // Test + ir.AddContainer(c) + if !reflect.DeepEqual(ir, want) { + t.Fatal("Failed to add the container properly. Expected:", want, "Actual:", ir) + } + }) + + t.Run("add an existing container to a filled IR", func(t *testing.T) { + // Setup + name1 := "name1" + new1 := true + c1 := types.NewContainer(name1, new1) + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + ir.Containers = append(ir.Containers, c1) + + p2 := plantypes.NewPlan() + want := types.NewIR(p2) + c2 := types.NewContainer(name1, new1) + want.Containers = append(want.Containers, c2) + + // Test + ir.AddContainer(c1) + if !reflect.DeepEqual(ir, want) { + t.Fatal("Failed to add the container properly. Expected:", want, "Actual:", ir) + } + }) +} + +func TestAddStorage(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("add a new storage to an empty IR", func(t *testing.T) { + // Setup + s := types.Storage{} + + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + + p2 := plantypes.NewPlan() + want := types.NewIR(p2) + want.Storages = append(want.Storages, s) + + // Test + ir.AddStorage(s) + if !reflect.DeepEqual(ir, want) { + t.Fatal("Failed to add the storage properly. Expected:", want, "Actual:", ir) + } + }) + + t.Run("add a existing storage to an filled IR", func(t *testing.T) { + // Setup + s := types.Storage{} + + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + ir.Storages = append(ir.Storages, s) + + p2 := plantypes.NewPlan() + want := types.NewIR(p2) + want.Storages = append(want.Storages, s) + + // Test + ir.AddStorage(s) + if !reflect.DeepEqual(ir, want) { + t.Fatal("Failed to add the storage properly. Expected:", want, "Actual:", ir) + } + }) +} + +func TestGetContainer(t *testing.T) { + log.SetLevel(log.DebugLevel) + + t.Run("get container for non existent image name from empty ir", func(t *testing.T) { + imgname1 := "imgname1" + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + if _, ok := ir.GetContainer(imgname1); ok { + t.Fatal("Should not have found the image name", imgname1, "in an empty ir.") + } + }) + + t.Run("get container for non existent image name from filled ir", func(t *testing.T) { + // Setup + name1 := "contname1" + new1 := true + c1 := types.NewContainer(name1, new1) + imgname1 := "imgname1" + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + ir.Containers = append(ir.Containers, c1) + + // Test + if _, ok := ir.GetContainer(imgname1); ok { + t.Fatal("Should not have found the non existent image name", imgname1, "in the ir.") + } + }) + + t.Run("get container for image name from filled ir", func(t *testing.T) { + // Setup + name1 := "contname1" + new1 := true + imgname1 := "imgname1" + + c1 := types.NewContainer(name1, new1) + c1.ImageNames = append(c1.ImageNames, imgname1) + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + ir.Containers = append(ir.Containers, c1) + + // Test + if _, ok := ir.GetContainer(imgname1); !ok { + t.Fatal("Failed to get the container for the image name", imgname1, "in the ir.") + } + }) + + t.Run("get container for image url from filled ir", func(t *testing.T) { + // Setup + name1 := "contname1" + new1 := true + registry1 := "registry1.com" + imgname1 := "imgname1" + imgurl1 := registry1 + "/namespace/" + imgname1 + + c1 := types.NewContainer(name1, new1) + c1.ImageNames = append(c1.ImageNames, imgname1) + p1 := plantypes.NewPlan() + ir := types.NewIR(p1) + ir.Containers = append(ir.Containers, c1) + ir.Kubernetes.RegistryURL = registry1 + + // Test + if _, ok := ir.GetContainer(imgurl1); !ok { + t.Fatal("Failed to get the container for the image url", imgurl1, "in the ir.") + } + }) +} diff --git a/misc/centos.repo b/misc/centos.repo new file mode 100644 index 000000000..8ab4f172e --- /dev/null +++ b/misc/centos.repo @@ -0,0 +1,13 @@ +[centos-8-baseos] +name = CentOS 8 (RPMs) - BaseOS +baseurl = http://mirror.centos.org/centos-8/8/BaseOS/x86_64/os/ +enabled = 1 +gpgkey = https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official +gpgcheck = 1 + +[centos-8-AppStream] +name = CentOS 8 (RPMs) - AppStream +baseurl = http://mirror.centos.org/centos-8/8/AppStream/x86_64/os/ +enabled = 1 +gpgkey = https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official +gpgcheck = 1 \ No newline at end of file diff --git a/samples/.m2kignore b/samples/.m2kignore new file mode 100644 index 000000000..9c558e357 --- /dev/null +++ b/samples/.m2kignore @@ -0,0 +1 @@ +. diff --git a/samples/docker-compose/api/Dockerfile b/samples/docker-compose/api/Dockerfile new file mode 100644 index 000000000..b585c7d64 --- /dev/null +++ b/samples/docker-compose/api/Dockerfile @@ -0,0 +1,6 @@ +FROM node:14 +WORKDIR /app +COPY . . +RUN npm install +EXPOSE 1234 +CMD ["npm", "run", "start"] diff --git a/samples/docker-compose/api/index.js b/samples/docker-compose/api/index.js new file mode 100644 index 000000000..2553a75a3 --- /dev/null +++ b/samples/docker-compose/api/index.js @@ -0,0 +1,51 @@ +import url from 'url'; +import http from 'http'; +import redis from "redis" + +const api_endpoint = '/fib'; +const usage_instructions = `usage: ${api_endpoint}?n=\n`; +const client = 'REDIS_URL' in process.env ? redis.createClient(process.env.REDIS_URL) : redis.createClient(); +client.on("error", err => console.error(err)); + +function requestHandler(req, res) { + const urlobj = url.parse(req.url, true); + + if (urlobj.pathname !== api_endpoint || !('n' in urlobj.query)) { + res.writeHead(400, { "Content-Type": "application/json" }); + return res.end(JSON.stringify({error:"invalid url",usage_instructions})); + } + + const n = parseInt(urlobj.query.n, 10); + if (isNaN(n)) { + res.writeHead(400, { "Content-Type": "application/json" }); + return res.end(JSON.stringify({error:"n is not a valid number"})); + } + + client.get(n, (err, ans) => { + if (err || ans === null) { + console.log(`CACHE MISS on n = ${n}`); + ans = fibonacci(n); + client.set(n, ans, err => { + if (err === null) console.log("cached", n); + else console.error("failed to cache the answer for n. error:", err) + }); + } else { + console.log(`CACHE HIT for n = ${n} ans is ${ans}`); + } + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ans})); + }); +} + +function fibonacci(n) { + let a = 0, b = 1, c = 1; + for(let i = 0; i < n; i++) { + c = a + b; + a = b; + b = c; + } + return a; +} + +// Main +http.createServer(requestHandler).listen(1234); diff --git a/samples/docker-compose/api/package-lock.json b/samples/docker-compose/api/package-lock.json new file mode 100644 index 000000000..d60e9b5a0 --- /dev/null +++ b/samples/docker-compose/api/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "api", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, + "redis": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz", + "integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==", + "requires": { + "denque": "^1.4.1", + "redis-commands": "^1.5.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + } + }, + "redis-commands": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", + "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + } + } +} diff --git a/samples/docker-compose/api/package.json b/samples/docker-compose/api/package.json new file mode 100644 index 000000000..24f38483a --- /dev/null +++ b/samples/docker-compose/api/package.json @@ -0,0 +1,13 @@ +{ + "type": "module", + "name": "api", + "version": "0.1.0", + "description": "API that calculate the nth fibonacci number", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "redis": "^3.0.2" + } +} diff --git a/samples/docker-compose/docker-compose.yaml b/samples/docker-compose/docker-compose.yaml new file mode 100644 index 000000000..f000bbced --- /dev/null +++ b/samples/docker-compose/docker-compose.yaml @@ -0,0 +1,19 @@ +version: '3' + +services: + api: + image: fibonacci-api:latest + build: ./api + ports: + - "1234:1234" + environment: + - REDIS_URL=redis://redis:6379 + redis: + image: redis + web: + image: fibonacci-web:latest + build: ./web + ports: + - "8080:80" + environment: + - API_URL=http://api:1234/fib diff --git a/samples/docker-compose/web/Dockerfile b/samples/docker-compose/web/Dockerfile new file mode 100644 index 000000000..6bfa46945 --- /dev/null +++ b/samples/docker-compose/web/Dockerfile @@ -0,0 +1,2 @@ +FROM php:7-apache +COPY . /var/www/html/ diff --git a/samples/docker-compose/web/fib.php b/samples/docker-compose/web/fib.php new file mode 100644 index 000000000..6149b6f92 --- /dev/null +++ b/samples/docker-compose/web/fib.php @@ -0,0 +1,31 @@ + + + + + + Fibonacci + + +

Answer

+

ans; + } else { + echo "Something went wrong. Please try again."; + echo "Error:" . $reply->error; + } + } else { + echo "You entered an invalid integer."; + } + ?>

+ Go back + + diff --git a/samples/docker-compose/web/index.php b/samples/docker-compose/web/index.php new file mode 100644 index 000000000..59d00524b --- /dev/null +++ b/samples/docker-compose/web/index.php @@ -0,0 +1,15 @@ + + + + + + Fibonacci + + +
+ + +
+

Enter an integer N to calculate the Nth fibonacci number.

+ + \ No newline at end of file diff --git a/samples/dockerfile/Dockerfile b/samples/dockerfile/Dockerfile new file mode 100644 index 000000000..fefaa47e1 --- /dev/null +++ b/samples/dockerfile/Dockerfile @@ -0,0 +1,6 @@ +FROM node:14 +WORKDIR /app +COPY . . +RUN npm install +EXPOSE 8080 +CMD ["npm", "run", "start"] diff --git a/samples/dockerfile/index.js b/samples/dockerfile/index.js new file mode 100644 index 000000000..93291d42a --- /dev/null +++ b/samples/dockerfile/index.js @@ -0,0 +1,7 @@ +import express from 'express' + +const port = 8080; + +const server = express(); +server.use(express.static("public")) +server.listen(port, () => console.log("Listening on port", port)); diff --git a/samples/dockerfile/package-lock.json b/samples/dockerfile/package-lock.json new file mode 100644 index 000000000..a3beb2cdd --- /dev/null +++ b/samples/dockerfile/package-lock.json @@ -0,0 +1,374 @@ +{ + "name": "dockerfile", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/samples/dockerfile/package.json b/samples/dockerfile/package.json new file mode 100644 index 000000000..b2a6c5692 --- /dev/null +++ b/samples/dockerfile/package.json @@ -0,0 +1,13 @@ +{ + "type": "module", + "name": "dockerfile", + "version": "0.1.0", + "description": "sample nodejs app that uses dockerfile", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/samples/dockerfile/public/about.html b/samples/dockerfile/public/about.html new file mode 100644 index 000000000..83458c7f6 --- /dev/null +++ b/samples/dockerfile/public/about.html @@ -0,0 +1,23 @@ + + + + + + About + + + +

About

+

This app uses dockerfile to maintain a consistent development environment.

+ + + \ No newline at end of file diff --git a/samples/dockerfile/public/index.html b/samples/dockerfile/public/index.html new file mode 100644 index 000000000..f0ae66143 --- /dev/null +++ b/samples/dockerfile/public/index.html @@ -0,0 +1,23 @@ + + + + + + Sample Dockerfile Nodejs App + + + +

Home

+

This is a sample nodejs app that uses dockerfile.

+ + + \ No newline at end of file diff --git a/samples/golang/main.go b/samples/golang/main.go new file mode 100644 index 000000000..1406bee7e --- /dev/null +++ b/samples/golang/main.go @@ -0,0 +1,33 @@ +/* +Copyright IBM Corporation 2020 + +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" + "log" + "net/http" +) + +func main() { + http.HandleFunc("/", func(writerObj http.ResponseWriter, requestObj *http.Request) { + fmt.Fprintf(writerObj, "This is a GOLANG web server app") + }) + + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatal("Web server error") + } +} diff --git a/samples/java-gradle/build.gradle b/samples/java-gradle/build.gradle new file mode 100644 index 000000000..1535b0789 --- /dev/null +++ b/samples/java-gradle/build.gradle @@ -0,0 +1,29 @@ +/* +Copyright IBM Corporation 2020 + +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. +*/ + +apply plugin: 'java' +apply plugin: 'war' +apply from: 'https://raw.github.com/gretty-gradle-plugin/gretty/master/pluginScripts/gretty.plugin' +apply plugin: 'eclipse-wtp' + +repositories { + mavenCentral() +} + +dependencies { + providedCompile 'javax.servlet:servlet-api:2.5' + runtime 'javax.servlet:jstl:1.1.2' +} \ No newline at end of file diff --git a/samples/java-gradle/src/main/java/simplewebapp/MainServlet.java b/samples/java-gradle/src/main/java/simplewebapp/MainServlet.java new file mode 100644 index 000000000..c045d7cbb --- /dev/null +++ b/samples/java-gradle/src/main/java/simplewebapp/MainServlet.java @@ -0,0 +1,28 @@ +/* +Copyright IBM Corporation 2020 + +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 simplewebapp; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.IOException; + +public class MainServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest requestObj, HttpServletResponse responseObj) throws ServletException, IOException { + responseObj.getOutputStream().println("

This is a gradle web app!

Its running on a web server

"); + } +} \ No newline at end of file diff --git a/samples/java-gradle/src/main/webapp/WEB-INF/web.xml b/samples/java-gradle/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..cf43d2162 --- /dev/null +++ b/samples/java-gradle/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,34 @@ + + + + + + + MainServlet + MainServlet + simplewebapp.MainServlet + + + + MainServlet + / + + + \ No newline at end of file diff --git a/samples/java-maven/pom.xml b/samples/java-maven/pom.xml new file mode 100644 index 000000000..ec00416c6 --- /dev/null +++ b/samples/java-maven/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + com.simplewebapp + simplewebapp + war + 1.0 + simplewebapp Maven Webapp + + simplewebapp + + diff --git a/samples/java-maven/src/main/webapp/WEB-INF/web.xml b/samples/java-maven/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..9259e3a4c --- /dev/null +++ b/samples/java-maven/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + + + + Maven Web App For Move2Kube + diff --git a/samples/java-maven/src/main/webapp/index.jsp b/samples/java-maven/src/main/webapp/index.jsp new file mode 100644 index 000000000..6e7d8b44a --- /dev/null +++ b/samples/java-maven/src/main/webapp/index.jsp @@ -0,0 +1,22 @@ + + + + +

This is a java-maven web app

+ + diff --git a/samples/nodejs/main.js b/samples/nodejs/main.js new file mode 100644 index 000000000..b4559eaed --- /dev/null +++ b/samples/nodejs/main.js @@ -0,0 +1,23 @@ +/* +Copyright IBM Corporation 2020 + +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. +*/ + +var serverObj = require('http'); +var PORT = 8080; + +serverObj.createServer(function (requestObj, responseObj) { + responseObj.write('

This is a node server

'); + responseObj.end(); +}).listen(PORT); diff --git a/samples/nodejs/package.json b/samples/nodejs/package.json new file mode 100644 index 000000000..20471ca81 --- /dev/null +++ b/samples/nodejs/package.json @@ -0,0 +1,9 @@ +{ + "name" : "main", + "version" : "0.1.0", + "description": "This is a test web server", + "keywords": ["test", "webserver"], + "scripts": { + "start": "node main.js" + } +} diff --git a/samples/php/src/simplewebapp.php b/samples/php/src/simplewebapp.php new file mode 100644 index 000000000..15fe874fb --- /dev/null +++ b/samples/php/src/simplewebapp.php @@ -0,0 +1,20 @@ + + +This is a PHP App"; +?> diff --git a/samples/python/main.py b/samples/python/main.py new file mode 100644 index 000000000..ebe800d62 --- /dev/null +++ b/samples/python/main.py @@ -0,0 +1,25 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +from flask import Flask + +simpleRestApiApp = Flask (__name__) + + +@simpleRestApiApp.route("/") +def api(): + return "This is a PYTHON web app" + +if __name__ == "__main__": + simpleRestApiApp.run(host='0.0.0.0', port=8080) \ No newline at end of file diff --git a/samples/python/requirements.txt b/samples/python/requirements.txt new file mode 100644 index 000000000..3422e07f4 --- /dev/null +++ b/samples/python/requirements.txt @@ -0,0 +1,15 @@ +# Copyright IBM Corporation 2020 +# +# 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. + +Flask==1.0.2 diff --git a/samples/ruby/Gemfile b/samples/ruby/Gemfile new file mode 100644 index 000000000..1d70bd234 --- /dev/null +++ b/samples/ruby/Gemfile @@ -0,0 +1,18 @@ +# Copyright 2020 Move2Kube 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. + +source "https://rubygems.org" +gem 'sinatra' +gem "puma" +gem "rails" diff --git a/samples/ruby/app.rb b/samples/ruby/app.rb new file mode 100644 index 000000000..efb136ffe --- /dev/null +++ b/samples/ruby/app.rb @@ -0,0 +1,22 @@ +# Copyright 2020 Move2Kube 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. + +require 'sinatra' + +set :bind, '0.0.0.0' +set :port, 8080 + +get '/' do + erb :main +end \ No newline at end of file diff --git a/samples/ruby/config.ru b/samples/ruby/config.ru new file mode 100644 index 000000000..f7f503c44 --- /dev/null +++ b/samples/ruby/config.ru @@ -0,0 +1,16 @@ +# Copyright 2020 Move2Kube 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. + +require './app' +run Sinatra::Application \ No newline at end of file diff --git a/samples/ruby/views/main.erb b/samples/ruby/views/main.erb new file mode 100644 index 000000000..602c83320 --- /dev/null +++ b/samples/ruby/views/main.erb @@ -0,0 +1,24 @@ +<% +=begin + +Copyright 2020 Move2Kube 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. + +=end +%> + + + +This is a RUBY App View + \ No newline at end of file diff --git a/scripts/installdeps.sh b/scripts/installdeps.sh new file mode 100644 index 000000000..693c1c320 --- /dev/null +++ b/scripts/installdeps.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Copyright IBM Corporation 2020 +# +# 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. + +if [ "$(uname)" == "Darwin" ]; then + PACKPLATFORM="macos" + KUBECTLPLATFORM="darwin" + OPERATORSDKPLATFORM="apple-darwin" + echo "Once this installation finishes, please do install docker from https://docs.docker.com/docker-for-mac/install/" +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + PACKPLATFORM="linux" + KUBECTLPLATFORM="linux" + OPERATORSDKPLATFORM="linux-gnu" + if ! grep -q container_t "/proc/1/attr/current"; then + curl -fsSL get.docker.com -o get-docker.sh && sudo sh get-docker.sh && rm get-docker.sh + fi +else + echo "Unsupported platform." + exit 1 +fi + +mkdir -p bin +curl -o pack.tgz -LJO https://github.com/buildpacks/pack/releases/download/v0.12.0/pack-v0.12.0-$PACKPLATFORM.tgz && tar -xzf pack.tgz && mv pack bin/ && rm pack.tgz +curl -o kubectl -LJO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/$KUBECTLPLATFORM/amd64/kubectl && mv kubectl bin/ +chmod +x bin/kubectl +curl -o operator-sdk -LJO https://github.com/operator-framework/operator-sdk/releases/download/v1.0.0/operator-sdk-v1.0.0-x86_64-$OPERATORSDKPLATFORM && mv operator-sdk bin/ +chmod +x bin/operator-sdk +echo "PATH=$PATH:$PWD/bin" >> ~/.bash_profile +source ~/.bash_profile diff --git a/scripts/licensecheck.sh b/scripts/licensecheck.sh new file mode 100755 index 000000000..3c00a1f95 --- /dev/null +++ b/scripts/licensecheck.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Copyright IBM Corporation 2020 +# +# 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. + +find_files_without_string() { + find . -type f \( -name '*.go' -o -name '*.sh' \) ! -exec grep -q -e "$1" \{\} \; -print +} + +didfail=0 + +filepaths=$(find_files_without_string 'Licensed under the Apache License, Version 2.0 (the "License")') + +if [[ $filepaths ]]; then + echo "The following files are missing the license:" + echo "$filepaths" + didfail=1 +fi + +filepaths=$(find_files_without_string 'IBM Corporation') + +if [[ $filepaths ]]; then + echo "The following files are missing the copyright:" + echo "$filepaths" + didfail=1 +fi + +exit "$didfail" diff --git a/types/collection/cfcontainerizers.go b/types/collection/cfcontainerizers.go new file mode 100644 index 000000000..ccf650d6f --- /dev/null +++ b/types/collection/cfcontainerizers.go @@ -0,0 +1,55 @@ +/* +Copyright IBM Corporation 2020 + +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 collection + +import ( + "github.com/konveyor/move2kube/types" + plantypes "github.com/konveyor/move2kube/types/plan" +) + +// CfContainerizersMetadataKind defines kind of cfcontainerizers +const CfContainerizersMetadataKind types.Kind = "CfContainerizers" + +// CfContainerizers is the file structure of cfcontainerizers +type CfContainerizers struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec CfContainerizersSpec `yaml:"spec,omitempty"` +} + +// CfContainerizersSpec stores the data +type CfContainerizersSpec struct { + BuildpackContainerizers []BuildpackContainerizer `yaml:"buildpackContainerizers"` +} + +// BuildpackContainerizer defines the structure of Buildpack and the containerization strategy to be used for it +type BuildpackContainerizer struct { + BuildpackName string `yaml:"buildpackName"` + ContainerBuildType plantypes.ContainerBuildTypeValue `yaml:"containerBuildType"` + ContainerizationTargetOptions []string `yaml:"targetOptions,omitempty"` +} + +// NewCfContainerizers creates new CfContainerizers instance +func NewCfContainerizers() CfContainerizers { + var cfcontainerizers = CfContainerizers{ + TypeMeta: types.TypeMeta{ + Kind: string(CfContainerizersMetadataKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + } + return cfcontainerizers +} diff --git a/types/collection/cfcontainerizers_test.go b/types/collection/cfcontainerizers_test.go new file mode 100644 index 000000000..ba1cadd1e --- /dev/null +++ b/types/collection/cfcontainerizers_test.go @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2020 + +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 collection_test + +import ( + "testing" + + "github.com/konveyor/move2kube/types" + "github.com/konveyor/move2kube/types/collection" +) + +func TestNewCfContainerizers(t *testing.T) { + cfs := collection.NewCfContainerizers() + if cfs.Kind != string(collection.CfContainerizersMetadataKind) || cfs.APIVersion != types.SchemeGroupVersion.String() { + t.Fatal("Failed to initialize CfContainerizers properly.") + } +} diff --git a/types/collection/cfinstanceapps.go b/types/collection/cfinstanceapps.go new file mode 100644 index 000000000..b82e8e45a --- /dev/null +++ b/types/collection/cfinstanceapps.go @@ -0,0 +1,59 @@ +/* +Copyright IBM Corporation 2020 + +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 collection + +import ( + "github.com/konveyor/move2kube/types" +) + +// CfInstanceAppsMetadataKind defines kind of cf runtime instance apps file +const CfInstanceAppsMetadataKind types.Kind = "CfInstanceApps" + +// CfInstanceApps defines definition of cf runtime instance apps file +type CfInstanceApps struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec CfInstanceAppsSpec `yaml:"spec,omitempty"` +} + +// CfInstanceAppsSpec stores the data +type CfInstanceAppsSpec struct { + CfApplications []CfApplication `yaml:"applications"` +} + +// CfApplication defines the structure of a cf runtime application +type CfApplication struct { + Name string `yaml:"name"` + Buildpack string `yaml:"buildpack,omitempty"` + DetectedBuildpack string `yaml:"detectedBuildpack,omitempty"` + Memory int64 `yaml:"memory"` + Instances int `yaml:"instances"` + DockerImage string `yaml:"dockerImage,omitempty"` + Ports []int32 `yaml:"ports"` + Env map[string]string `yaml:"env,omitempty"` +} + +// NewCfInstanceApps creates a new instance of CfInstanceApps +func NewCfInstanceApps() CfInstanceApps { + var cfInstanceApps = CfInstanceApps{ + TypeMeta: types.TypeMeta{ + Kind: string(CfInstanceAppsMetadataKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + } + return cfInstanceApps +} diff --git a/types/collection/cfinstanceapps_test.go b/types/collection/cfinstanceapps_test.go new file mode 100644 index 000000000..826f171f2 --- /dev/null +++ b/types/collection/cfinstanceapps_test.go @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2020 + +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 collection_test + +import ( + "testing" + + "github.com/konveyor/move2kube/types" + "github.com/konveyor/move2kube/types/collection" +) + +func TestNewCfInstanceApps(t *testing.T) { + cfapps := collection.NewCfInstanceApps() + if cfapps.Kind != string(collection.CfInstanceAppsMetadataKind) || cfapps.APIVersion != types.SchemeGroupVersion.String() { + t.Fatal("Failed to initialize CfInstanceApps properly.") + } +} diff --git a/types/collection/cluster.go b/types/collection/cluster.go new file mode 100644 index 000000000..0411256c6 --- /dev/null +++ b/types/collection/cluster.go @@ -0,0 +1,129 @@ +/* +Copyright IBM Corporation 2020 + +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 collection + +import ( + common "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/types" +) + +// ClusterMetadataKind defines the kind of cluster metadata file +const ClusterMetadataKind types.Kind = "ClusterMetadata" + +// ClusterMetadata for collect output +type ClusterMetadata struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec ClusterMetadataSpec `yaml:"spec,omitempty"` +} + +// ClusterMetadataSpec stores the data +type ClusterMetadataSpec struct { + StorageClasses []string `yaml:"storageClasses"` + APIKindVersionMap map[string][]string `yaml:"apiKindVersionMap"` //[kubernetes kind]["gv1", "gv2",...,"gvn"] prioritized group-version +} + +// Merge helps merge clustermetadata +func (c *ClusterMetadata) Merge(newc ClusterMetadata) bool { + if newc.isEmpty() { + return true + } + if c.isEmpty() { + c.Kind = newc.Kind + c.Name = newc.Name + } else if c.Kind != newc.Kind { + // If neither metadata is empty then their kinds should match + return false + } + if newc.Name != "" { + c.Name = newc.Name + } + + // Allow only intersection of storage classes + newslice := []string{} + for _, sc := range c.Spec.StorageClasses { + if common.IsStringPresent(newc.Spec.StorageClasses, sc) { + newslice = append(newslice, sc) + } + } + c.Spec.StorageClasses = newslice + if len(c.Spec.StorageClasses) == 0 { + c.Spec.StorageClasses = []string{"default"} + } + //TODO: Do Intelligent merge of version + apiversionkindmap := make(map[string][]string) + for kindname, gvList := range newc.Spec.APIKindVersionMap { + if _, ok := c.Spec.APIKindVersionMap[kindname]; ok { + apiversionkindmap[kindname] = gvList + } + } + c.Spec.APIKindVersionMap = apiversionkindmap + return true +} + +// Merge helps merge clustermetadata +func (c *ClusterMetadataSpec) Merge(newc ClusterMetadataSpec) bool { + // Allow only intersection of storage classes + newslice := []string{} + for _, sc := range c.StorageClasses { + if common.IsStringPresent(newc.StorageClasses, sc) { + newslice = append(newslice, sc) + } + } + c.StorageClasses = newslice + //TODO: Do Intelligent merge of version + apiversionkindmap := make(map[string][]string) + for kindname, gvList := range newc.APIKindVersionMap { + if _, ok := c.APIKindVersionMap[kindname]; ok { + apiversionkindmap[kindname] = gvList + } + } + c.APIKindVersionMap = apiversionkindmap + return true +} + +func (c *ClusterMetadata) isEmpty() bool { + return c.Kind == "" +} + +// GetSupportedVersions returns all the group version supported for the kind in this cluster +func (c *ClusterMetadataSpec) GetSupportedVersions(kind string) []string { + if gvList, ok := c.APIKindVersionMap[kind]; ok { + if len(gvList) > 0 { + return gvList + } + } + return nil +} + +// NewClusterMetadata creates a new cluster metadata instance +func NewClusterMetadata(contextName string) ClusterMetadata { + var clusterMetadata = ClusterMetadata{ + TypeMeta: types.TypeMeta{ + Kind: string(ClusterMetadataKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + ObjectMeta: types.ObjectMeta{ + Name: contextName, + }, + Spec: ClusterMetadataSpec{ + StorageClasses: []string{}, + APIKindVersionMap: make(map[string][]string), + }, + } + return clusterMetadata +} diff --git a/types/collection/cluster_test.go b/types/collection/cluster_test.go new file mode 100644 index 000000000..dbb4a064d --- /dev/null +++ b/types/collection/cluster_test.go @@ -0,0 +1,138 @@ +/* +Copyright IBM Corporation 2020 + +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 collection_test + +import ( + "reflect" + "testing" + + "github.com/konveyor/move2kube/types" + "github.com/konveyor/move2kube/types/collection" +) + +func TestMerge(t *testing.T) { + t.Run("merging 2 empty metadatas", func(t *testing.T) { + cmeta1 := collection.NewClusterMetadata("") + cmeta1.Kind = "" + cmeta2 := collection.NewClusterMetadata("") + cmeta2.Kind = "" + want := collection.NewClusterMetadata("") + want.Kind = "" + if merged := cmeta1.Merge(cmeta2); !merged || !reflect.DeepEqual(cmeta1, want) { + t.Fatal("Failed to merge ClusterMetadata properly. Expected:", want, "Actual:", cmeta1) + } + }) + + t.Run("merging a non empty metadata into an empty metadata", func(t *testing.T) { + cmeta1 := collection.NewClusterMetadata("") + cmeta1.Kind = "" + + cmeta2 := collection.NewClusterMetadata("ctxname1") + + want := collection.NewClusterMetadata("") + want.Name = "ctxname1" + want.Spec.StorageClasses = []string{"default"} + + if merged := cmeta1.Merge(cmeta2); !merged || !reflect.DeepEqual(cmeta1, want) { + t.Fatal("Failed to merge ClusterMetadata properly. Expected:", want, "Actual:", cmeta1) + } + }) + + t.Run("merging metadata with different kinds", func(t *testing.T) { + cmeta1 := collection.NewClusterMetadata("") + cmeta1.Kind = "kind1" + + cmeta2 := collection.NewClusterMetadata("") + cmeta2.Kind = "kind2" + + if merged := cmeta1.Merge(cmeta2); merged { + t.Fatal("Should not have merged metadata with different kinds. The kinds are", cmeta1.Kind, "and", cmeta2.Kind) + } + }) + + t.Run("merging version maps from filled metadata into filled metadata", func(t *testing.T) { + key1 := "key1" + val1 := []string{"1.0.0", "1.1.0", "1.1.1"} + key2 := "key2" + val2 := []string{"2.0.0", "2.2.0", "2.2.2"} + + cmeta1 := collection.NewClusterMetadata("") + cmeta1.Spec.APIKindVersionMap = map[string][]string{key1: val1} + + cmeta2 := collection.NewClusterMetadata("") + cmeta2.Spec.APIKindVersionMap = map[string][]string{key1: val2, key2: val2} + + want := collection.NewClusterMetadata("") + want.Spec.StorageClasses = []string{"default"} + want.Spec.APIKindVersionMap = map[string][]string{key1: val2} + + if merged := cmeta1.Merge(cmeta2); !merged || !reflect.DeepEqual(cmeta1, want) { + t.Fatal("Failed to merge ClusterMetadata properly. Expected:", want, "Actual:", cmeta1) + } + }) + + t.Run("merging storage classes from filled metadata into filled metadata", func(t *testing.T) { + cmeta1 := collection.NewClusterMetadata("") + cmeta1.Spec.StorageClasses = []string{"111", "222", "333"} + + cmeta2 := collection.NewClusterMetadata("") + cmeta2.Spec.StorageClasses = []string{"222", "333", "444"} + + want := collection.NewClusterMetadata("") + want.Spec.StorageClasses = []string{"222", "333"} + + if merged := cmeta1.Merge(cmeta2); !merged || !reflect.DeepEqual(cmeta1, want) { + t.Fatal("Failed to merge ClusterMetadata properly. Expected:", want, "Actual:", cmeta1) + } + }) +} + +func TestGetSupportedVersions(t *testing.T) { + t.Run("get nil for non existent key", func(t *testing.T) { + key1 := "foobar_non_existent_key" + cmeta := collection.NewClusterMetadata("") + if arr := cmeta.Spec.GetSupportedVersions(key1); arr != nil { + t.Fatal("The method should have returned nil since the key", key1, "is not present in the APIKindVersionMap.") + } + }) + + t.Run("get nil for key with empty list of supported versions", func(t *testing.T) { + key1 := "key1" + cmeta := collection.NewClusterMetadata("") + cmeta.Spec.APIKindVersionMap = map[string][]string{key1: {}} + if arr := cmeta.Spec.GetSupportedVersions(key1); arr != nil { + t.Fatal("The method should have returned nil since the supported versions for the", key1, "is an empty list.") + } + }) + + t.Run("get a list of versions for a valid key", func(t *testing.T) { + key1 := "key1" + val1 := []string{"0.1.0", "0.1.1", "1.2.3"} + cmeta := collection.NewClusterMetadata("") + cmeta.Spec.APIKindVersionMap = map[string][]string{key1: val1} + if arr := cmeta.Spec.GetSupportedVersions(key1); arr == nil { + t.Fatal("The method did not return the correct list of supported versions for the", key1, "Expected:", val1, "Actual:", arr) + } + }) +} + +func TestNewClusterMetadata(t *testing.T) { + cmeta := collection.NewClusterMetadata("") + if cmeta.Kind != string(collection.ClusterMetadataKind) || cmeta.APIVersion != types.SchemeGroupVersion.String() { + t.Fatal("Failed to initialize ClusterMetadata properly.") + } +} diff --git a/types/collection/image.go b/types/collection/image.go new file mode 100644 index 000000000..419f1bfad --- /dev/null +++ b/types/collection/image.go @@ -0,0 +1,50 @@ +/* +Copyright IBM Corporation 2020 + +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 collection + +import ( + "github.com/konveyor/move2kube/types" +) + +// ImageMetadataKind defines kind for imagemetadata file +const ImageMetadataKind types.Kind = "ImageMetadata" + +// ImageInfo stores data about different images +type ImageInfo struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec ImageInfoSpec `yaml:"spec,omitempty"` +} + +// ImageInfoSpec defines the data stored about ImageInfo +type ImageInfoSpec struct { + Tags []string `yaml:"tags"` + PortsToExpose []int `yaml:"ports"` + AccessedDirs []string `yaml:"accessedDirs"` + UserID int `yaml:"userID"` +} + +// NewImageInfo creates a new imageinfo instance +func NewImageInfo() ImageInfo { + var imageInfo = ImageInfo{ + TypeMeta: types.TypeMeta{ + Kind: string(ImageMetadataKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + } + return imageInfo +} diff --git a/types/collection/image_test.go b/types/collection/image_test.go new file mode 100644 index 000000000..a172fd3c6 --- /dev/null +++ b/types/collection/image_test.go @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2020 + +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 collection_test + +import ( + "testing" + + "github.com/konveyor/move2kube/types" + "github.com/konveyor/move2kube/types/collection" +) + +func TestNewImageInfo(t *testing.T) { + img := collection.NewImageInfo() + if img.Kind != string(collection.ImageMetadataKind) || img.APIVersion != types.SchemeGroupVersion.String() { + t.Fatal("Failed to initialize ImageInfo properly.") + } +} diff --git a/types/info/versioninfo.go b/types/info/versioninfo.go new file mode 100644 index 000000000..fb0775ba9 --- /dev/null +++ b/types/info/versioninfo.go @@ -0,0 +1,100 @@ +/* +Copyright IBM Corporation 2020 + +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 info + +import ( + "runtime" + + semver "github.com/Masterminds/semver/v3" + log "github.com/sirupsen/logrus" +) + +var ( + // Update this whenever making a new release. + // The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata] + // Given a version number MAJOR.MINOR.PATCH, increment the: + // MAJOR version when you make incompatible API changes, + // MINOR version when you add functionality in a backwards compatible manner, and + // PATCH version when you make backwards compatible bug fixes. + // Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + // For more details about semver 2 see https://semver.org/ + version = "v0.1.0" + + // metadata is extra build time data + buildmetadata = "" + // gitCommit is the git sha1 + gitCommit = "" + // gitTreeState is the state of the git tree + gitTreeState = "" +) + +// GetVersion returns the semver string of the version +func GetVersion() string { + if buildmetadata == "" { + return version + } + return version + "+" + buildmetadata +} + +// GetVersionInfo returns version info +func GetVersionInfo() VersionInfo { + v := VersionInfo{ + Version: GetVersion(), + GitCommit: gitCommit, + GitTreeState: gitTreeState, + GoVersion: runtime.Version(), + } + return v +} + +// VersionInfo describes the compile time information. +type VersionInfo struct { + // Version is the current semver. + Version string `yaml:"version,omitempty"` + // GitCommit is the git sha1. + GitCommit string `yaml:"gitCommit,omitempty"` + // GitTreeState is the state of the git tree. + GitTreeState string `yaml:"gitTreeState,omitempty"` + // GoVersion is the version of the Go compiler used. + GoVersion string `yaml:"goVersion,omitempty"` +} + +// IsSameVersion checks if two versions are same and logs a message if the version is newer or older +func (v *VersionInfo) IsSameVersion() bool { + binaryversion, err := semver.NewVersion(GetVersion()) + if err != nil { + log.Warnf("Unable to load current version of binary : %s", err) + return false + } + objversion, err := semver.NewVersion(v.Version) + if err != nil { + log.Warnf("Unable to load current version : %s", err) + return false + } + + compare := binaryversion.Compare(objversion) + + if compare == 0 { + return true + } + if compare < 0 { + log.Warnf("The file version (%s) is newer than the binary version (%s).", objversion, binaryversion) + } else { + log.Warnf("The file version (%s) is older than the binary version (%s).", objversion, binaryversion) + } + return false +} diff --git a/types/info/versioninfo_test.go b/types/info/versioninfo_test.go new file mode 100644 index 000000000..0cef91e86 --- /dev/null +++ b/types/info/versioninfo_test.go @@ -0,0 +1,63 @@ +/* +Copyright IBM Corporation 2020 + +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 info_test + +import ( + "testing" + + "github.com/konveyor/move2kube/types/info" +) + +func TestGetVersionInfo(t *testing.T) { + vinfo := info.GetVersionInfo() + if !vinfo.IsSameVersion() { + t.Fatal("Versions don't match. Expected:", info.GetVersion(), "Actual:", vinfo.Version) + } +} + +func TestIsSameVersion(t *testing.T) { + t.Run("same version", func(t *testing.T) { + vinfo := info.GetVersionInfo() + if !vinfo.IsSameVersion() { + t.Fatal("The versions are same but the method says they are different. Binary version:", info.GetVersion(), "Object version:", vinfo.Version) + } + }) + + t.Run("older version", func(t *testing.T) { + vinfo := info.GetVersionInfo() + vinfo.Version = "0.0.0" + if vinfo.IsSameVersion() { + t.Fatal("The versions are different but the method says they are equal. Binary version:", info.GetVersion(), "Object version:", vinfo.Version) + } + }) + + t.Run("newer version", func(t *testing.T) { + vinfo := info.GetVersionInfo() + vinfo.Version = "100.0.0" + if vinfo.IsSameVersion() { + t.Fatal("The versions are different but the method says they are equal. Binary version:", info.GetVersion(), "Object version:", vinfo.Version) + } + }) + + t.Run("invalid version", func(t *testing.T) { + vinfo := info.GetVersionInfo() + vinfo.Version = "foobar" + if vinfo.IsSameVersion() { + t.Fatal("The versions are different but the method says they are equal. Binary version:", info.GetVersion(), "Object version:", vinfo.Version) + } + }) +} diff --git a/types/output/helmvaluesoutput.go b/types/output/helmvaluesoutput.go new file mode 100644 index 000000000..00ef457df --- /dev/null +++ b/types/output/helmvaluesoutput.go @@ -0,0 +1,77 @@ +/* +Copyright IBM Corporation 2020 + +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 output + +const ( + // ParameterRegistryPrefix defines the parameter registry + ParameterRegistryPrefix string = "{{.Values.registryurl}}/{{.Values.registrynamespace}}/" + // ServicesTag is the tag name for services + ServicesTag string = "services" + // ImageTagTag is the tag name for images + ImageTagTag string = "imagetag" + // ContainersTag is the tag name for conatiners + ContainersTag string = "containers" +) + +// HelmValues defines the format of values.yaml +type HelmValues struct { + RegistryURL string `yaml:"registryurl"` + RegistryNamespace string `yaml:"registrynamespace"` + Services map[string]Service `yaml:"services"` + StorageClass string `yaml:"storageclass,omitempty"` + GlobalVariables map[string]string `yaml:"globalvariables,omitempty"` +} + +// Merge helps merge helmvalues +func (h *HelmValues) Merge(newh HelmValues) { + if newh.RegistryNamespace != "" { + h.RegistryNamespace = newh.RegistryNamespace + } + if newh.RegistryURL != "" { + h.RegistryURL = newh.RegistryURL + } + if newh.StorageClass != "" { + h.StorageClass = newh.StorageClass + } + for gvkey, gvval := range newh.GlobalVariables { + h.GlobalVariables[gvkey] = gvval + } + for serviceName, service := range newh.Services { + if _, ok := h.Services[serviceName]; !ok { + h.Services[serviceName] = service + } else { + for ncn, nc := range service.Containers { + if c, ok := h.Services[serviceName].Containers[ncn]; !ok { + h.Services[serviceName].Containers[ncn] = nc + } else { + c.TagName = nc.TagName + h.Services[serviceName].Containers[ncn] = c + } + } + } + } +} + +// Service stores the metadata about the services and its containers +type Service struct { + Containers map[string]Container `yaml:"containers"` +} + +// Container stores the metadata the container +type Container struct { + TagName string `yaml:"imagetag"` +} diff --git a/types/output/helmvaluesoutput_test.go b/types/output/helmvaluesoutput_test.go new file mode 100644 index 000000000..abd81edff --- /dev/null +++ b/types/output/helmvaluesoutput_test.go @@ -0,0 +1,105 @@ +/* +Copyright IBM Corporation 2020 + +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 output_test + +import ( + "reflect" + "testing" + + "github.com/konveyor/move2kube/types/output" +) + +func TestMerge(t *testing.T) { + t.Run("merge 2 empty helm values", func(t *testing.T) { + h1 := output.HelmValues{} + h2 := output.HelmValues{} + want := output.HelmValues{} + if h1.Merge(h2); !reflect.DeepEqual(h1, want) { + t.Fatal("The value should not have changed after merge. Expected:", want, "Actual:", h1) + } + }) + + t.Run("merge filled helm value into filled helm value", func(t *testing.T) { + h1 := output.HelmValues{} + h1.RegistryNamespace = "namespace1" + h1.RegistryURL = "url1" + h1.StorageClass = "storagecls1" + h2 := output.HelmValues{} + h2.RegistryNamespace = "namespace2" + h2.RegistryURL = "url2" + h2.StorageClass = "storagecls2" + want := output.HelmValues{} + want.RegistryNamespace = "namespace2" + want.RegistryURL = "url2" + want.StorageClass = "storagecls2" + if h1.Merge(h2); !reflect.DeepEqual(h1, want) { + t.Fatal("Failed to merge the helm values properly. Expected:", want, "Actual:", h1) + } + }) + + t.Run("merge global and service variables into filled helm value", func(t *testing.T) { + makeH := func() output.HelmValues { + h := output.HelmValues{} + h.GlobalVariables = make(map[string]string) + return h + } + key1 := "key1" + val1 := "val1" + val2 := "val2" + + h1 := makeH() + h1.GlobalVariables[key1] = val1 + + h2 := makeH() + h2.GlobalVariables[key1] = val2 + + want := makeH() + want.GlobalVariables[key1] = val2 + + if h1.Merge(h2); !reflect.DeepEqual(h1, want) { + t.Fatal("Failed to merge the helm values properly. Expected:", want, "Actual:", h1) + } + }) + + t.Run("merge ImageTagTree properly into filled helm value", func(t *testing.T) { + makeH := func() output.HelmValues { + h := output.HelmValues{} + h.Services = make(map[string]output.Service) + return h + } + key1 := "key1" + key2 := "key2" + con1 := "name1" + val1 := output.Container{"tag1"} + val2 := output.Container{"tag2"} + + h1 := makeH() + h1.Services[key1] = output.Service{map[string]output.Container{con1: val1}} + + h2 := makeH() + h2.Services[key1] = output.Service{map[string]output.Container{con1: val2}} + h2.Services[key2] = output.Service{map[string]output.Container{con1: val1}} + + want := makeH() + want.Services[key1] = output.Service{map[string]output.Container{con1: val2}} + want.Services[key2] = output.Service{map[string]output.Container{con1: val1}} + + if h1.Merge(h2); !reflect.DeepEqual(h1, want) { + t.Fatal("Failed to merge the helm values properly. Expected:", want, "Actual:", h1) + } + }) +} diff --git a/types/plan/plan.go b/types/plan/plan.go new file mode 100644 index 000000000..d1365d73b --- /dev/null +++ b/types/plan/plan.go @@ -0,0 +1,393 @@ +/* +Copyright IBM Corporation 2020 + +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 plan + +import ( + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/types" +) + +// SourceTypeValue defines the type of source +type SourceTypeValue string + +// ContainerBuildTypeValue defines the containerization type +type ContainerBuildTypeValue string + +// TranslationTypeValue defines the translation type +type TranslationTypeValue string + +// TargetInfoArtifactTypeValue defines the target info type +type TargetInfoArtifactTypeValue string + +// BuildArtifactTypeValue defines the build artifact type +type BuildArtifactTypeValue string + +// SourceArtifactTypeValue defines the source artifact type +type SourceArtifactTypeValue string + +// TargetArtifactTypeValue defines the target artifact type +type TargetArtifactTypeValue string + +// PlanKind is kind of plan file +const PlanKind types.Kind = "Plan" + +const ( + // Compose2KubeTranslation translation type is used when source is docker compose + Compose2KubeTranslation TranslationTypeValue = "Compose2Kube" + // CfManifest2KubeTranslation translation type is used when source is cloud foundry manifest + CfManifest2KubeTranslation TranslationTypeValue = "Cfmanifest2Kube" + // Any2KubeTranslation translation type is used when source is of an unknown platform + Any2KubeTranslation TranslationTypeValue = "Any2Kube" + // Kube2KubeTranslation translation type is used when source is Kubernetes + Kube2KubeTranslation TranslationTypeValue = "Kube2Kube" + // Knative2KubeTranslation translation type is used when source is Knative + Knative2KubeTranslation TranslationTypeValue = "Knative2Kube" + // Dockerfile2KubeTranslation translation type is used when source is Knative + Dockerfile2KubeTranslation TranslationTypeValue = "Dockerfile2Kube" +) + +const ( + // ComposeSourceTypeValue defines the source as docker compose + ComposeSourceTypeValue SourceTypeValue = "DockerCompose" + // DirectorySourceTypeValue defines the source as a simple directory + DirectorySourceTypeValue SourceTypeValue = "Directory" + // CfManifestSourceTypeValue defines the source as cf manifest + CfManifestSourceTypeValue SourceTypeValue = "CfManifest" + // KNativeSourceTypeValue defines the source as KNative + KNativeSourceTypeValue SourceTypeValue = "Knative" + // K8sSourceTypeValue defines the source as Kubernetes + K8sSourceTypeValue SourceTypeValue = "Kubernetes" +) + +const ( + // DockerFileContainerBuildTypeValue defines the containerization type as docker file + DockerFileContainerBuildTypeValue ContainerBuildTypeValue = "NewDockerfile" + // ReuseDockerFileContainerBuildTypeValue defines the containerization type as reuse of dockerfile + ReuseDockerFileContainerBuildTypeValue ContainerBuildTypeValue = "ReuseDockerfile" + // ReuseContainerBuildTypeValue defines the containerization type as reuse of an existing container + ReuseContainerBuildTypeValue ContainerBuildTypeValue = "Reuse" + // CNBContainerBuildTypeValue defines the containerization type of cloud native buildpack + CNBContainerBuildTypeValue ContainerBuildTypeValue = "CNB" + // ManualContainerBuildTypeValue defines that the tool assumes that the image will be created manually + ManualContainerBuildTypeValue ContainerBuildTypeValue = "Manual" + // S2IContainerBuildTypeValue defines the containerization type of S2I + S2IContainerBuildTypeValue ContainerBuildTypeValue = "S2I" +) + +const ( + // K8sFileArtifactType defines the source artifact type of K8s + K8sFileArtifactType SourceArtifactTypeValue = "Kubernetes" + // KnativeFileArtifactType defines the source artifact type of KNative + KnativeFileArtifactType SourceArtifactTypeValue = "Knative" + // ComposeFileArtifactType defines the source artifact type of Docker compose + ComposeFileArtifactType SourceArtifactTypeValue = "DockerCompose" + // ImageInfoArtifactType defines the source artifact type of image info + ImageInfoArtifactType SourceArtifactTypeValue = "ImageInfo" + // CfManifestArtifactType defines the source artifact type of cf manifest + CfManifestArtifactType SourceArtifactTypeValue = "CfManifest" + // CfRunningManifestArtifactType defines the source artifact type of a manifest of a running instance + CfRunningManifestArtifactType SourceArtifactTypeValue = "CfRunningManifest" + // SourceDirectoryArtifactType defines the source artifact type of normal source code directory + SourceDirectoryArtifactType SourceArtifactTypeValue = "SourceCode" + // DockerfileArtifactType defines the source artifact type of dockerfile + DockerfileArtifactType SourceArtifactTypeValue = "Dockerfile" +) + +const ( + // SourceDirectoryBuildArtifactType defines source data artifact type + SourceDirectoryBuildArtifactType BuildArtifactTypeValue = "SourceCode" +) + +const ( + // K8sClusterArtifactType defines target info + K8sClusterArtifactType TargetInfoArtifactTypeValue = "KubernetesCluster" +) + +const ( + // Helm defines helm artifact type + Helm TargetArtifactTypeValue = "Helm" + // Yamls defines K8s artifact type + Yamls TargetArtifactTypeValue = "Yamls" + // Knative defines Knative artifact type + Knative TargetArtifactTypeValue = "Knative" +) + +// Plan defines the format of plan +type Plan struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec PlanSpec `yaml:"spec,omitempty"` +} + +// PlanSpec stores the data about the plan +type PlanSpec struct { + Inputs Inputs `yaml:"inputs"` + Outputs Outputs `yaml:"outputs"` +} + +// Outputs defines the output section of plan +type Outputs struct { + Kubernetes KubernetesOutput `yaml:"kubernetes"` +} + +// KubernetesOutput defines the output format for kubernetes deployable artifacts +type KubernetesOutput struct { + RegistryURL string `yaml:"registryURL,omitempty"` + RegistryNamespace string `yaml:"registryNamespace,omitempty"` + ArtifactType TargetArtifactTypeValue `yaml:"artifactType"` + ClusterType string `yaml:"clusterType,omitempty"` + IgnoreUnsupportedKinds bool `yaml:"ignoreUnsupportedKinds,omitempty"` +} + +// Merge allows merge of two Kubernetes Outputs +func (output *KubernetesOutput) Merge(newoutput KubernetesOutput) { + if newoutput != (KubernetesOutput{}) { + if newoutput.RegistryURL != "" { + output.RegistryURL = newoutput.RegistryURL + } + if newoutput.RegistryNamespace != "" { + output.RegistryNamespace = newoutput.RegistryNamespace + } + output.ArtifactType = newoutput.ArtifactType + output.IgnoreUnsupportedKinds = newoutput.IgnoreUnsupportedKinds + if newoutput.ClusterType != "" { + output.ClusterType = newoutput.ClusterType + } + } +} + +// Inputs defines the input section of plan +type Inputs struct { + RootDir string `yaml:"rootDir"` + K8sFiles []string `yaml:"kubernetesYamls,omitempty"` + QACaches []string `yaml:"qaCaches,omitempty"` + Services map[string][]Service `yaml:"services"` // [serviceName][Services] + TargetInfoArtifacts map[TargetInfoArtifactTypeValue][]string `yaml:"targetInfoArtifacts,omitempty"` //[targetinfoartifacttype][List of artifacts] +} + +// Service defines a plan service +type Service struct { + ServiceName string `yaml:"serviceName"` + Image string `yaml:"image"` + TranslationType TranslationTypeValue `yaml:"translationType"` + ContainerBuildType ContainerBuildTypeValue `yaml:"containerBuildType"` + SourceTypes []SourceTypeValue `yaml:"sourceType"` + ContainerizationTargetOptions []string `yaml:"targetOptions,omitempty"` + SourceArtifacts map[SourceArtifactTypeValue][]string `yaml:"sourceArtifacts"` //[translationartifacttype][List of artifacts] + BuildArtifacts map[BuildArtifactTypeValue][]string `yaml:"buildArtifacts,omitempty"` //[buildartifacttype][List of artifacts] + UpdateContainerBuildPipeline bool `yaml:"updateContainerBuildPipeline"` + UpdateDeployPipeline bool `yaml:"updateDeployPipeline"` +} + +func (service *Service) merge(newservice Service) bool { + if service.ServiceName != newservice.ServiceName || service.Image != newservice.Image || service.TranslationType != newservice.TranslationType || service.ContainerBuildType != newservice.ContainerBuildType { + return false + } + if len(service.BuildArtifacts[SourceDirectoryBuildArtifactType]) > 0 && len(newservice.BuildArtifacts[SourceDirectoryBuildArtifactType]) > 0 && service.BuildArtifacts[SourceDirectoryBuildArtifactType][0] != newservice.BuildArtifacts[SourceDirectoryBuildArtifactType][0] { + return false + } + service.UpdateContainerBuildPipeline = service.UpdateContainerBuildPipeline || newservice.UpdateContainerBuildPipeline + service.UpdateDeployPipeline = service.UpdateDeployPipeline || newservice.UpdateDeployPipeline + service.addSourceTypes(newservice.SourceTypes) + service.addTargetOptions(newservice.ContainerizationTargetOptions) + service.addSourceArtifacts(newservice.SourceArtifacts) + service.addBuildArtifacts(newservice.BuildArtifacts) + return true +} + +// AddSourceArtifact adds a source artifact to a plan service +func (service *Service) AddSourceArtifact(sat SourceArtifactTypeValue, value string) { + if val, ok := service.SourceArtifacts[sat]; ok { + service.SourceArtifacts[sat] = append(val, value) + } else { + service.SourceArtifacts[sat] = []string{value} + } +} + +func (service *Service) addSourceArtifactArray(sat SourceArtifactTypeValue, values []string) { + if val, ok := service.SourceArtifacts[sat]; ok { + service.SourceArtifacts[sat] = common.MergeStringSlices(val, values) + } else { + service.SourceArtifacts[sat] = values + } +} + +func (service *Service) addSourceArtifacts(sats map[SourceArtifactTypeValue][]string) { + for key2, value2 := range sats { + service.addSourceArtifactArray(key2, value2) + } +} + +// AddBuildArtifact adds a build artifact to a plan service +func (service *Service) AddBuildArtifact(sat BuildArtifactTypeValue, value string) { + if val, ok := service.BuildArtifacts[sat]; ok { + service.BuildArtifacts[sat] = append(val, value) + } else { + service.BuildArtifacts[sat] = []string{value} + } +} + +func (service *Service) addBuildArtifactArray(sat BuildArtifactTypeValue, values []string) { + if val, ok := service.BuildArtifacts[sat]; ok { + service.BuildArtifacts[sat] = common.MergeStringSlices(val, values) + } else { + service.BuildArtifacts[sat] = values + } +} + +func (service *Service) addBuildArtifacts(sats map[BuildArtifactTypeValue][]string) { + for key2, value2 := range sats { + service.addBuildArtifactArray(key2, value2) + } +} + +// AddSourceType adds source type to a plan service +func (service *Service) AddSourceType(st SourceTypeValue) bool { + found := false + for _, est := range service.SourceTypes { + if est == st { + found = true + break + } + } + if !found { + service.SourceTypes = append(service.SourceTypes, st) + } + return true +} + +// addSourceTypes adds source types to a plan service +func (service *Service) addSourceTypes(sts []SourceTypeValue) { + for _, st := range sts { + service.AddSourceType(st) + } +} + +// addTargetOption adds target option to a plan service +func (service *Service) addTargetOption(st string) bool { + found := false + for _, est := range service.ContainerizationTargetOptions { + if est == st { + found = true + break + } + } + if !found { + service.ContainerizationTargetOptions = append(service.ContainerizationTargetOptions, st) + } + return true +} + +// addTargetOptions adds target options to a plan service +func (service *Service) addTargetOptions(sts []string) { + for _, st := range sts { + service.addTargetOption(st) + } +} + +// GetFullPath returns the full path with rootdir, unless the directory is in assets path +func (p Plan) GetFullPath(path string) string { + if strings.HasPrefix(path, common.AssetsDir) { + return filepath.Join(common.TempPath, path) + } + return filepath.Join(p.Spec.Inputs.RootDir, path) +} + +// GetRelativePath returns the relative path with respect to the rootdir, unless the directory is in assets path +func (p Plan) GetRelativePath(path string) (string, error) { + if strings.HasPrefix(path, common.TempPath) { + rel, err := filepath.Rel(common.TempPath, path) + if err != nil { + return "", err + } + return rel, nil + } + rel, err := filepath.Rel(p.Spec.Inputs.RootDir, path) + if err != nil { + return "", err + } + return rel, nil +} + +// AddServicesToPlan adds a list of services to a plan +func (p *Plan) AddServicesToPlan(services []Service) { + for _, service := range services { + if _, ok := p.Spec.Inputs.Services[service.ServiceName]; !ok { + p.Spec.Inputs.Services[service.ServiceName] = []Service{} + log.Debugf("Added new service to plan : %s", service.ServiceName) + } + merged := false + existingServices := p.Spec.Inputs.Services[service.ServiceName] + for i := range existingServices { + if existingServices[i].merge(service) { + merged = true + } + } + if !merged { + p.Spec.Inputs.Services[service.ServiceName] = append(p.Spec.Inputs.Services[service.ServiceName], service) + } + } +} + +// NewPlan creates a new plan +// Sets the version and optionally fills in some default values +func NewPlan() Plan { + plan := Plan{ + TypeMeta: types.TypeMeta{ + Kind: string(PlanKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + ObjectMeta: types.ObjectMeta{ + Name: common.DefaultProjectName, + }, + Spec: PlanSpec{ + Inputs: Inputs{ + Services: make(map[string][]Service), + TargetInfoArtifacts: make(map[TargetInfoArtifactTypeValue][]string), + }, + Outputs: Outputs{ + Kubernetes: KubernetesOutput{ + ArtifactType: Yamls, + ClusterType: common.DefaultClusterType, + IgnoreUnsupportedKinds: false, + }, + }, + }, + } + return plan +} + +// NewService creates a new service +func NewService(servicename string, translationtype TranslationTypeValue) Service { + var service Service + service.ServiceName = servicename + service.Image = servicename + ":latest" + service.TranslationType = translationtype + service.SourceTypes = make([]SourceTypeValue, 0) + service.ContainerBuildType = ReuseContainerBuildTypeValue + service.BuildArtifacts = make(map[BuildArtifactTypeValue][]string) + service.SourceArtifacts = make(map[SourceArtifactTypeValue][]string) + service.UpdateDeployPipeline = false + service.UpdateContainerBuildPipeline = false + + return service +} diff --git a/types/plan/plan_test.go b/types/plan/plan_test.go new file mode 100644 index 000000000..3b5ee02b5 --- /dev/null +++ b/types/plan/plan_test.go @@ -0,0 +1,444 @@ +/* +Copyright IBM Corporation 2020 + +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 plan_test + +import ( + "path/filepath" + "reflect" + "testing" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/types/plan" +) + +func TestMerge(t *testing.T) { + t.Run("merge new empty k8s output into empty k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{} + out2 := plan.KubernetesOutput{} + want := plan.KubernetesOutput{} + out1.Merge(out2) + if out1 != want { + t.Fatal("The output should not have changed. Expected:", want, "Actual:", out1) + } + }) + t.Run("merge artifact type and ignore supported kinds from new k8s output into filled k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{"111", "222", "333", "444", false} + out2 := plan.KubernetesOutput{ArtifactType: "type1", IgnoreUnsupportedKinds: true} + want := out1 + want.ArtifactType = "type1" + want.IgnoreUnsupportedKinds = true + out1.Merge(out2) + if out1 != want { + t.Fatal("Failed to merge the fields properly. Expected:", want, "Actual:", out1) + } + }) + t.Run("merge registry url from new k8s output into filled k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{"111", "222", "333", "444", false} + out2 := plan.KubernetesOutput{ArtifactType: "type1", IgnoreUnsupportedKinds: true, RegistryURL: "url1"} + want := out1 + want.ArtifactType = "type1" + want.IgnoreUnsupportedKinds = true + want.RegistryURL = "url1" + out1.Merge(out2) + if out1 != want { + t.Fatal("Failed to merge the fields properly. Expected:", want, "Actual:", out1) + } + }) + t.Run("merge registry namespace from new k8s output into filled k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{"111", "222", "333", "444", false} + out2 := plan.KubernetesOutput{ArtifactType: "type1", IgnoreUnsupportedKinds: true, RegistryNamespace: "namespace1"} + want := out1 + want.ArtifactType = "type1" + want.IgnoreUnsupportedKinds = true + want.RegistryNamespace = "namespace1" + out1.Merge(out2) + if out1 != want { + t.Fatal("Failed to merge the fields properly. Expected:", want, "Actual:", out1) + } + }) + t.Run("merge image pull secret from new k8s output into filled k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{"111", "222", "333", "444", false} + out2 := plan.KubernetesOutput{ArtifactType: "type1", IgnoreUnsupportedKinds: true} + want := out1 + want.ArtifactType = "type1" + want.IgnoreUnsupportedKinds = true + out1.Merge(out2) + if out1 != want { + t.Fatal("Failed to merge the fields properly. Expected:", want, "Actual:", out1) + } + }) + t.Run("merge cluster type from new k8s output into filled k8s output", func(t *testing.T) { + out1 := plan.KubernetesOutput{"111", "222", "333", "444", false} + out2 := plan.KubernetesOutput{ArtifactType: "type1", IgnoreUnsupportedKinds: true, ClusterType: "clus_type1"} + want := out1 + want.ArtifactType = "type1" + want.IgnoreUnsupportedKinds = true + want.ClusterType = "clus_type1" + out1.Merge(out2) + if out1 != want { + t.Fatal("Failed to merge the fields properly. Expected:", want, "Actual:", out1) + } + }) +} + +func TestAddSourceArtifact(t *testing.T) { + // Setup + s := plan.NewService("foo", "bar") + var key1 plan.SourceArtifactTypeValue = "key1" + val1 := "val1" + val2 := "val2" + // Test + t.Run("add source artifact to empty service", func(t *testing.T) { + s.AddSourceArtifact(key1, val1) + if arr, ok := s.SourceArtifacts[key1]; !ok || len(arr) != 1 || arr[0] != val1 { + t.Fatal("Failed to add source artifact type properly. Expected:", []string{val1}, "Actual:", arr) + } + }) + // Setup + s = plan.NewService("foo", "bar") + // Test + t.Run("add source artifact to filled service", func(t *testing.T) { + s.AddSourceArtifact(key1, val1) + s.AddSourceArtifact(key1, val2) + if arr, ok := s.SourceArtifacts[key1]; !ok || len(arr) != 2 || arr[1] != val2 { + t.Fatal("Failed to add source artifact type properly when array is not empty. Expected:", []string{val1, val2}, "Actual:", arr) + } + }) +} + +func TestAddBuildArtifact(t *testing.T) { + // Setup + s := plan.NewService("foo", "bar") + var key1 plan.BuildArtifactTypeValue = "key1" + val1 := "val1" + val2 := "val2" + // Test + t.Run("add build artifact to empty service", func(t *testing.T) { + s.AddBuildArtifact(key1, val1) + if arr, ok := s.BuildArtifacts[key1]; !ok || len(arr) != 1 || arr[0] != val1 { + t.Fatal("Failed to add build artifact type properly. Expected:", []string{val1}, "Actual:", arr) + } + }) + // Setup + s = plan.NewService("foo", "bar") + // Test + t.Run("add build artifact to filled service", func(t *testing.T) { + s.AddBuildArtifact(key1, val1) + s.AddBuildArtifact(key1, val2) + if arr, ok := s.BuildArtifacts[key1]; !ok || len(arr) != 2 || arr[1] != val2 { + t.Fatal("Failed to add build artifact type properly when array is not empty. Expected:", []string{val1, val2}, "Actual:", arr) + } + }) +} + +func TestAddSourceType(t *testing.T) { + // Setup + var src1 plan.SourceTypeValue = "src1" + var src2 plan.SourceTypeValue = "src2" + svc0 := plan.NewService("foo", "bar") + svc1 := plan.NewService("foo", "bar") + svc1.AddSourceType(src1) + svc2 := plan.NewService("foo", "bar") + svc2.AddSourceType(src2) + // Test + tcs := []struct { + name string + svc plan.Service + src plan.SourceTypeValue + shouldIncrease bool + }{ + {name: "add source to empty service", svc: svc0, src: src1, shouldIncrease: true}, + {name: "skip adding source to filled service", svc: svc1, src: src1, shouldIncrease: false}, + {name: "add source to filled service", svc: svc2, src: src1, shouldIncrease: true}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + prevLen := len(tc.svc.SourceTypes) + tc.svc.AddSourceType(tc.src) + if tc.shouldIncrease { + if len(tc.svc.SourceTypes) != prevLen+1 { + t.Fatal("Expected len of source types:", prevLen+1, ". Actual length:", len(tc.svc.SourceTypes)) + } + } else { + if len(tc.svc.SourceTypes) != prevLen { + t.Fatal("Expected service to skip adding the source", tc.src, ". Actual:", tc.svc) + } + } + }) + } +} + +func TestGetFullPath(t *testing.T) { + tempPath := common.TempPath + assetsDir := common.AssetsDir + assetsPath := common.AssetsPath + root := "tests/getfullpath/root" + if root == assetsPath { + root += "1234" + } + j := filepath.Join + // Assuming paths don't need to be cleaned since GetFullPath is an internal functions. + // Paths should be cleaned when first taken as input. + var testcases = []struct{ in, out string }{ + {j("foo"), j(root, "foo")}, + {j("foo", "bar"), j(root, "foo", "bar")}, + {j("foo", assetsPath, "bar"), j(root, "foo", assetsPath, "bar")}, + {j(tempPath, assetsDir, "foo"), j(root, tempPath, assetsDir, "foo")}, + {j(assetsDir, "foo"), j(tempPath, assetsDir, "foo")}, + {j(assetsDir, "foo", "bar"), j(tempPath, assetsDir, "foo", "bar")}, + } + + p := plan.NewPlan() + p.Spec.Inputs.RootDir = root + for _, testcase := range testcases { + if res := p.GetFullPath(testcase.in); res != testcase.out { + t.Error("Input:", testcase.in, "Expected:", testcase.out, "Actual:", res) + } + } +} + +func TestGetRelativePath(t *testing.T) { + assetsPath := common.AssetsPath + assetsDir := common.AssetsDir + root := "tests/getfullpath/root" + if root == assetsDir { + root += "1234" + } + j := filepath.Join + /* + // Since the result is a relative path it will contain ../ in cases like the one shown below: + root := "foo/bar" + input := "foo/abc" + GetRelativePath(input) == "../abc" + */ + var testcases = []struct { + in string + out string + fail bool + }{ + {j(root, "foo"), j("foo"), false}, + {j(root, "foo", "bar"), j("foo", "bar"), false}, + {j(root, "foo", assetsDir, "bar"), j("foo", assetsDir, "bar"), false}, + {j(assetsPath, "foo"), j(assetsDir, "foo"), false}, + {j(assetsPath, "foo", "bar"), j(assetsDir, "foo", "bar"), false}, + {j("/", "foo", "bar"), "", true}, + } + + p := plan.NewPlan() + p.Spec.Inputs.RootDir = root + for _, testcase := range testcases { + res, err := p.GetRelativePath(testcase.in) + if testcase.fail { + if err == nil { + t.Error("Input:", testcase.in, "Expected testcase to fail. Actual:", res, err) + } + } else { + if err != nil { + t.Error("Input:", testcase.in, "Expected testcase to succeed. Actual:", res, err) + } else if res != testcase.out { + t.Error("Input:", testcase.in, "Expected:", testcase.out, "Actual:", res, err) + } + } + } +} + +func TestAddServicesToPlan(t *testing.T) { + t.Run("add all services to empty plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + services := []plan.Service{ + plan.NewService("111", "111"), + plan.NewService("222", "222"), + plan.NewService("333", "333"), + } + // Test + p.AddServicesToPlan(services) + for _, s := range services { + if _, ok := p.Spec.Inputs.Services[s.ServiceName]; !ok { + t.Error("Failed to add the service", s, "Actual:", p) + } else { + es := p.Spec.Inputs.Services[s.ServiceName] + if len(es) != 1 || !reflect.DeepEqual(es[0], s) { + t.Error("Failed to merge the service", s, "correctly. Actual:", p) + } + } + } + }) + + t.Run("merge all services to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + p.Spec.Inputs.Services["222"] = []plan.Service{plan.NewService("222", "222")} + p.Spec.Inputs.Services["333"] = []plan.Service{plan.NewService("333", "333"), plan.NewService("333", "444")} + services := []plan.Service{ + plan.NewService("111", "111"), + plan.NewService("222", "222"), + plan.NewService("333", "333"), + plan.NewService("333", "444"), + } + want := plan.NewPlan() + want.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + want.Spec.Inputs.Services["222"] = []plan.Service{plan.NewService("222", "222")} + want.Spec.Inputs.Services["333"] = []plan.Service{plan.NewService("333", "333"), plan.NewService("333", "444")} + // Test + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get merged into existing services properly. Expected:", want, "Actual:", p) + } + }) + + t.Run("merge some services and add some services to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + p.Spec.Inputs.Services["222"] = []plan.Service{plan.NewService("222", "222")} + p.Spec.Inputs.Services["333"] = []plan.Service{plan.NewService("333", "333"), plan.NewService("333", "444")} + svc1 := plan.NewService("444", "444") + svc1.BuildArtifacts[plan.SourceDirectoryBuildArtifactType] = []string{"src1"} + svc2 := plan.NewService("444", "444") + svc2.BuildArtifacts[plan.SourceDirectoryBuildArtifactType] = []string{"src2"} + services := []plan.Service{ + plan.NewService("111", "111"), + plan.NewService("222", "222"), + plan.NewService("333", "333"), + svc1, + svc2, + } + want := plan.NewPlan() + want.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + want.Spec.Inputs.Services["222"] = []plan.Service{plan.NewService("222", "222")} + want.Spec.Inputs.Services["333"] = []plan.Service{plan.NewService("333", "333"), plan.NewService("333", "444")} + want.Spec.Inputs.Services[svc1.ServiceName] = []plan.Service{svc1, svc2} + // Test + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get added and merged into existing services properly. Expected:", want, "Actual:", p) + } + }) + + t.Run("merge all services having target options to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + + svc1 := plan.NewService("111", "111") + svc1.ContainerizationTargetOptions = []string{"opt1"} + services := []plan.Service{svc1} + + want := plan.NewPlan() + svc2 := plan.NewService("111", "111") + svc2.ContainerizationTargetOptions = []string{"opt1"} + want.Spec.Inputs.Services["111"] = []plan.Service{svc2} + + // Test + p.AddServicesToPlan(services) + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get merged into existing services properly. Expected:", want, "Actual:", p) + } + }) + + t.Run("merge all services having source types to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + + svc1 := plan.NewService("111", "111") + svc1.SourceTypes = []plan.SourceTypeValue{"type1"} + services := []plan.Service{svc1} + + want := plan.NewPlan() + svc2 := plan.NewService("111", "111") + svc2.SourceTypes = []plan.SourceTypeValue{"type1"} + want.Spec.Inputs.Services["111"] = []plan.Service{svc2} + + // Test + p.AddServicesToPlan(services) + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get merged into existing services properly. Expected:", want, "Actual:", p) + } + }) + + t.Run("merge all services having build artifacts to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + + var key1 plan.BuildArtifactTypeValue = "111" + + svc1 := plan.NewService("111", "111") + svc1.BuildArtifacts[key1] = []string{"art1"} + services := []plan.Service{svc1} + + want := plan.NewPlan() + svc2 := plan.NewService("111", "111") + svc2.BuildArtifacts[key1] = []string{"art1"} + want.Spec.Inputs.Services["111"] = []plan.Service{svc2} + + // Test + p.AddServicesToPlan(services) + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get merged into existing services properly. Expected:", want, "Actual:", p) + } + }) + + t.Run("merge all services having source artifacts to filled plan", func(t *testing.T) { + // Setup + p := plan.NewPlan() + p.Spec.Inputs.Services["111"] = []plan.Service{plan.NewService("111", "111")} + + var key1 plan.SourceArtifactTypeValue = "111" + + svc1 := plan.NewService("111", "111") + svc1.SourceArtifacts[key1] = []string{"art1"} + services := []plan.Service{svc1} + + want := plan.NewPlan() + svc2 := plan.NewService("111", "111") + svc2.SourceArtifacts[key1] = []string{"art1"} + want.Spec.Inputs.Services["111"] = []plan.Service{svc2} + + // Test + p.AddServicesToPlan(services) + p.AddServicesToPlan(services) + if !reflect.DeepEqual(want, p) { + t.Fatal("The new services didn't get merged into existing services properly. Expected:", want, "Actual:", p) + } + }) +} + +func TestNewPlan(t *testing.T) { + p := plan.NewPlan() + if p.Spec.Inputs.Services == nil || p.Spec.Inputs.TargetInfoArtifacts == nil { + t.Error("Failed to instantiate the plan fields properly. Actual:", p) + } +} + +func TestNewService(t *testing.T) { + svc_name := "foo" + var trans_type plan.TranslationTypeValue = "bar" + s := plan.NewService(svc_name, trans_type) + if s.ServiceName != svc_name || s.TranslationType != trans_type { + t.Error("Service name and translation type have not been set correctly. Expected:", svc_name, trans_type, "Actual:", s.ServiceName, s.TranslationType) + } + if s.SourceTypes == nil || s.BuildArtifacts == nil || s.SourceArtifacts == nil { + t.Error("Failed to instantiate the service fields properly. Actual:", s) + } +} diff --git a/types/qaengine/cache.go b/types/qaengine/cache.go new file mode 100644 index 000000000..dfe2aeee5 --- /dev/null +++ b/types/qaengine/cache.go @@ -0,0 +1,130 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + log "github.com/sirupsen/logrus" + + "github.com/konveyor/move2kube/internal/common" + "github.com/konveyor/move2kube/types" +) + +// QACacheKind defines kind of cfcontainerizers +const QACacheKind types.Kind = "QACache" + +// Cache stores the answers for reuse +type Cache struct { + types.TypeMeta `yaml:",inline"` + types.ObjectMeta `yaml:"metadata,omitempty"` + Spec CacheSpec `yaml:"spec,omitempty"` +} + +// CacheSpec stores the cache data +type CacheSpec struct { + file string `yaml:"-"` + // Problems stores the list of problems with resolutions + Problems []Problem `yaml:"solutions"` +} + +// NewCache creates new cache instance +func NewCache(file string) Cache { + c := Cache{ + TypeMeta: types.TypeMeta{ + Kind: string(QACacheKind), + APIVersion: types.SchemeGroupVersion.String(), + }, + Spec: CacheSpec{ + file: file, + }, + } + return c +} + +// Load loads and merges cache +func (cache *Cache) Load() error { + c := Cache{} + err := common.ReadYaml(cache.Spec.file, &c) + if err != nil { + log.Errorf("Unable to load cache : %s", err) + } else { + cache.merge(c) + for i := range cache.Spec.Problems { + cache.Spec.Problems[i].Resolved = true + } + } + return err +} + +// Write writes cache to disk +func (cache *Cache) Write() error { + err := common.WriteYaml(cache.Spec.file, cache) + if err != nil { + log.Warnf("Unable to write cache : %s", err) + } + return err +} + +// AddProblemSolutionToCache adds a problem to solution cache +func (cache *Cache) AddProblemSolutionToCache(p Problem) bool { + if !p.Resolved { + log.Warnf("Unresolved problem. Not going to be added to cache.") + return false + } + added := false + for i, cp := range cache.Spec.Problems { + if cp.matches(p) { + log.Warnf("A solution already exists in cache for [%s], rewriting", p.Desc) + cache.Spec.Problems[i] = p + added = true + break + } + } + if !added { + cache.Spec.Problems = append(cache.Spec.Problems, p) + } + if err := cache.Write(); err != nil { + log.Errorf("Unable to persist cache : %s", err) + } + return true +} + +// GetSolution reads a solution for the problem +func (cache *Cache) GetSolution(p Problem) (ans Problem, err error) { + if p.Resolved { + log.Warnf("Problem already solved.") + return p, nil + } + for _, cp := range cache.Spec.Problems { + if cp.matches(p) && cp.Resolved { + err := p.SetAnswer(cp.Solution.Answer) + return p, err + } + } + return p, nil +} + +func (cache *Cache) merge(c Cache) { + for _, p := range c.Spec.Problems { + for _, op := range cache.Spec.Problems { + if op.matches(p) { + log.Warnf("There are two answers for %s in cache. Ignoring latter ones.", p.Desc) + continue + } + } + cache.Spec.Problems = append(cache.Spec.Problems, p) + } +} diff --git a/types/qaengine/cache_test.go b/types/qaengine/cache_test.go new file mode 100644 index 000000000..a648ab557 --- /dev/null +++ b/types/qaengine/cache_test.go @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine_test + +import ( + "testing" + + "github.com/konveyor/move2kube/types" + "github.com/konveyor/move2kube/types/qaengine" +) + +func TestNewCache(t *testing.T) { + c := qaengine.NewCache("cache.yaml") + if c.Kind != string(qaengine.QACacheKind) || c.APIVersion != types.SchemeGroupVersion.String() { + t.Fatal("Failed to initialize QACache properly.") + } +} diff --git a/types/qaengine/doc.go b/types/qaengine/doc.go new file mode 100644 index 000000000..583efb8e9 --- /dev/null +++ b/types/qaengine/doc.go @@ -0,0 +1,20 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine contains the types used for the question answering part of the CLI. +*/ +package qaengine diff --git a/types/qaengine/problem.go b/types/qaengine/problem.go new file mode 100644 index 000000000..d30676f7e --- /dev/null +++ b/types/qaengine/problem.go @@ -0,0 +1,288 @@ +/* +Copyright IBM Corporation 2020 + +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 qaengine + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "sync" + + "github.com/konveyor/move2kube/internal/common" + log "github.com/sirupsen/logrus" +) + +// SolutionFormType is the type that defines different types of solutions possible +type SolutionFormType string + +const ( + // SelectSolutionFormType defines a single select solution type + SelectSolutionFormType SolutionFormType = "Select" + // MultiSelectSolutionFormType defines a multi-select solution type + MultiSelectSolutionFormType SolutionFormType = "MultiSelect" + // InputSolutionFormType allows single line user input + InputSolutionFormType SolutionFormType = "Input" + // MultilineSolutionFormType allows multiple user input + MultilineSolutionFormType SolutionFormType = "MultiLine" + // PasswordSolutionFormType allows password entry + PasswordSolutionFormType SolutionFormType = "Password" + // ConfirmSolutionFormType allows yes/no answers + ConfirmSolutionFormType SolutionFormType = "Confirm" +) + +var ( + lastAssignedProblemID = 0 // keep track of IDs + problemIDIncrementMutex = &sync.Mutex{} // manage incrementing of problem ids atomically +) + +// Problem defines the QA problem +type Problem struct { + ID int `yaml:"-" json:"id"` + Desc string `yaml:"description" json:"description"` + Context []string `yaml:"context,omitempty" json:"context,omitempty"` + Solution SolutionForm `yaml:"solution" json:"solution,omitempty"` + Resolved bool `yaml:"resolved,omitempty" json:"resolved,omitempty"` +} + +// SolutionForm defines the solution +type SolutionForm struct { + Type SolutionFormType `yaml:"type" json:"type"` + Default []string `yaml:"default,omitempty" json:"default,omitempty"` + Options []string `yaml:"options,omitempty" json:"options,omitempty"` + Answer []string `yaml:"answer" json:"answer"` +} + +// SetAnswer sets the answer +func (p *Problem) SetAnswer(answer []string) error { + if p.Solution.Type != MultiSelectSolutionFormType && len(answer) != 1 { + return fmt.Errorf("The question type is not multiselect, but there are multiple answers") + } + if p.Solution.Type == SelectSolutionFormType || p.Solution.Type == MultiSelectSolutionFormType { + success := true + p.Solution.Answer = []string{} + for _, a := range answer { + if !common.IsStringPresent(p.Solution.Options, a) { + log.Warnf("No matching value in options for %s. Ignoring.", a) + success = false + continue + } + p.Solution.Answer = append(p.Solution.Answer, a) + } + if !success { + return fmt.Errorf("Unknown options selected") + } + p.Resolved = true + return nil + } + if p.Solution.Type == ConfirmSolutionFormType { + _, err := strconv.ParseBool(answer[0]) + if err != nil { + log.Warnf("Error while parsing answer for confirm question type : %s", err) + return err + } + } + p.Solution.Answer = []string{answer[0]} + p.Resolved = true + return nil +} + +// GetSliceAnswer returns a slice as answer if the solution type supports it +func (p *Problem) GetSliceAnswer() (ans []string, err error) { + if !p.Resolved { + return ans, fmt.Errorf("Problem yet to be resolved") + } + if p.Solution.Type != MultiSelectSolutionFormType { + return p.Solution.Answer, fmt.Errorf("This question type does not support this answer type") + } + return p.Solution.Answer, nil +} + +// GetBoolAnswer returns a bool as answer if the solution type supports it +func (p *Problem) GetBoolAnswer() (ans bool, err error) { + if !p.Resolved { + return ans, fmt.Errorf("Problem yet to be resolved") + } + if p.Solution.Type == ConfirmSolutionFormType { + return false, fmt.Errorf("This question type does not ssupport this answer type") + } + if len(p.Solution.Answer) != 1 { + return false, fmt.Errorf("No answer available") + } + ans, err = strconv.ParseBool(p.Solution.Answer[0]) + if err != nil { + return false, err + } + return ans, nil +} + +// GetStringAnswer returns a string as answer if the solution type supports it +func (p *Problem) GetStringAnswer() (ans string, err error) { + if !p.Resolved { + return ans, fmt.Errorf("Problem yet to be resolved") + } + if p.Solution.Type == MultiSelectSolutionFormType || p.Solution.Type == ConfirmSolutionFormType { + return "", fmt.Errorf("This question type does not ssupport this answer type") + } + if len(p.Solution.Answer) != 1 { + return "", fmt.Errorf("Wrong number of answers") + } + return p.Solution.Answer[0], nil +} + +// Matches checks if the problems are same +func (p *Problem) matches(np Problem) bool { + if !p.matchString(p.Desc, np.Desc) || p.Solution.Type != np.Solution.Type { + return false + } + return true +} + +// Compares str1 with str2 in a case-insensitive manner +// Tries to compile str1 as a regex and check for full match +func (p *Problem) matchString(str1 string, str2 string) bool { + if strings.EqualFold(str1, str2) { + return true + } + r, err := regexp.MatchString(str1, str2) + if err != nil { + log.Debugf("Unable to compile string %s : %s", str1, err) + return false + } + return r +} + +func newProblem(t SolutionFormType, desc string, context []string, def []string, opts []string) (p Problem, err error) { + resolved := false + answer := []string{} + if desc == "" { + return p, fmt.Errorf("Empty Description") + } + switch t { + case MultiSelectSolutionFormType: + if len(opts) == 0 { + resolved = true + } + if len(def) > 0 { + for _, d := range def { + if !common.IsStringPresent(opts, d) { + return p, fmt.Errorf("Default value [%s] not present in options [%+v]", d, opts) + } + } + } + case SelectSolutionFormType: + if len(opts) == 0 { + return p, fmt.Errorf("Atleast one option is required for question %s", desc) + } + if len(opts) == 1 { + answer = opts + resolved = true + } + if len(def) > 1 { + log.Warnf("Only one default is allowed for question %s. Setting default as first value %s", desc, def) + def = []string{def[0]} + } + if len(def) == 0 { + def = []string{opts[0]} + } else { + if !common.IsStringPresent(opts, def[0]) { + return p, fmt.Errorf("Default value [%s] not present in options [%+v]", def[0], opts) + } + } + case ConfirmSolutionFormType: + if len(opts) > 0 { + log.Warnf("Options is not required for confirm question type : %s", desc) + } + if len(def) > 1 { + log.Warnf("Only one default is allowed for question %s.", desc) + } + if len(def) == 0 { + def = []string{strconv.FormatBool(false)} + } else { + _, err := strconv.ParseBool(def[0]) + if err != nil { + log.Warnf("Unable to parse default value %s. Setting as false", def[0]) + def = []string{strconv.FormatBool(false)} + } + def = []string{def[0]} + } + case InputSolutionFormType, MultilineSolutionFormType: + if len(def) > 1 { + log.Warnf("Only one default value supported for %s. Ignoring others.", desc) + def = []string{def[0]} + } + if len(opts) > 0 { + log.Warnf("Options not supported for %s. Ignoring options.", desc) + opts = []string{} + } + case PasswordSolutionFormType: + if len(def) > 0 { + log.Warnf("Default not supported for %s. Ignoring default.", desc) + def = []string{} + } + if len(opts) > 0 { + log.Warnf("Options not supported for %s. Ignoring options.", desc) + opts = []string{} + } + } + return Problem{ + ID: getProblemID(), + Desc: desc, + Context: context, + Solution: SolutionForm{Type: t, Default: def, Options: opts, Answer: answer}, + Resolved: resolved, + }, nil +} + +// getProblemID returns a new problem id +func getProblemID() int { + problemIDIncrementMutex.Lock() + lastAssignedProblemID++ + currID := lastAssignedProblemID + problemIDIncrementMutex.Unlock() + return currID +} + +// NewSelectProblem creates a new instance of select problem +func NewSelectProblem(desc string, context []string, def string, opts []string) (p Problem, err error) { + return newProblem(SelectSolutionFormType, desc, context, []string{def}, opts) +} + +// NewMultiSelectProblem creates a new instance of multiselect problem +func NewMultiSelectProblem(desc string, context []string, def []string, opts []string) (p Problem, err error) { + return newProblem(MultiSelectSolutionFormType, desc, context, def, opts) +} + +// NewConfirmProblem creates a new instance of confirm problem +func NewConfirmProblem(desc string, context []string, def bool) (p Problem, err error) { + return newProblem(ConfirmSolutionFormType, desc, context, []string{strconv.FormatBool(def)}, []string{}) +} + +// NewInputProblem creates a new instance of input problem +func NewInputProblem(desc string, context []string, def string) (p Problem, err error) { + return newProblem(InputSolutionFormType, desc, context, []string{def}, []string{}) +} + +// NewMultilineInputProblem creates a new instance of multiline input problem +func NewMultilineInputProblem(desc string, context []string, def string) (p Problem, err error) { + return newProblem(MultilineSolutionFormType, desc, context, []string{def}, []string{}) +} + +// NewPasswordProblem creates a new instance of password problem +func NewPasswordProblem(desc string, context []string) (p Problem, err error) { + return newProblem(PasswordSolutionFormType, desc, context, []string{}, []string{}) +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 000000000..c26a7f689 --- /dev/null +++ b/types/types.go @@ -0,0 +1,52 @@ +/* +Copyright IBM Corporation 2020 + +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 types + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // AppName represents the full app name + AppName string = "move2kube" + // AppNameShort represents the short app name + AppNameShort string = "m2k" + // GroupName is the group name use in this package + GroupName = AppName + ".io" +) + +// Kind stores the kind of the file +type Kind string + +// TypeMeta stores apiversion and kind for resources +type TypeMeta struct { + // APIVersion defines the versioned schema of this representation of an object. + APIVersion string `yaml:"apiVersion,omitempty"` + // Kind is a string value representing the resource this object represents. + Kind string `json:"kind,omitempty"` +} + +// ObjectMeta stores object metadata +type ObjectMeta struct { + // Name represents the name of the resource + Name string `json:"name,omitempty"` +} + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} +)