diff --git a/cmd/commands/system.go b/cmd/commands/system.go index cf8c87f69..adf0703cf 100644 --- a/cmd/commands/system.go +++ b/cmd/commands/system.go @@ -37,9 +37,20 @@ func SystemInstall(kc *core.KubectlClient) *cobra.Command { Short: "Install riff and Knative system components", Long: `Install riff and Knative system components -If an 'istio-system' namespace isn't found then the it will be created and Istio components will be installed. +If an 'istio-system' namespace isn't found, it will be created and Istio components will be installed. -Use the '--node-port' flag when installing on Minikube and other clusters that don't support an external load balancer.' +Use the '--node-port' flag when installing on Minikube and other clusters that don't support an external load balancer. + +Use the '--manifest' flag to specify the path of a manifest file which provides the URLs of the YAML definitions of the +components to be installed. The manifest file contents should be of the following form: + +version: 0.1 +istio: + - https://path/to/istio-release.yaml +knative: + - https://path/to/serving-release.yaml + - https://path/to/eventing-release.yaml + - https://path/to/stub-bus-release.yaml `, Example: ` riff system install`, PreRunE: func(cmd *cobra.Command, args []string) error { @@ -68,6 +79,7 @@ Use the '--node-port' flag when installing on Minikube and other clusters that d }, } + command.Flags().StringVarP(&options.Manifest, "manifest", "m", "", "file path of a manifest file referring to the YAML files to be applied") command.Flags().BoolVarP(&options.NodePort, "node-port", "", false, "whether to use NodePort instead of LoadBalancer for ingress gateways") command.Flags().BoolVarP(&options.Force, "force", "", false, "force the install of components without getting any prompts") diff --git a/docs/riff_system_install.md b/docs/riff_system_install.md index 476f3e053..db847cb74 100644 --- a/docs/riff_system_install.md +++ b/docs/riff_system_install.md @@ -6,9 +6,20 @@ Install riff and Knative system components Install riff and Knative system components -If an 'istio-system' namespace isn't found then the it will be created and Istio components will be installed. +If an 'istio-system' namespace isn't found, it will be created and Istio components will be installed. -Use the '--node-port' flag when installing on Minikube and other clusters that don't support an external load balancer.' +Use the '--node-port' flag when installing on Minikube and other clusters that don't support an external load balancer. + +Use the '--manifest' flag to specify the path of a manifest file which provides the URLs of the YAML definitions of the +components to be installed. The manifest file contents should be of the following form: + +version: 0.1 +istio: + - https://path/to/istio-release.yaml +knative: + - https://path/to/serving-release.yaml + - https://path/to/eventing-release.yaml + - https://path/to/stub-bus-release.yaml ``` @@ -24,9 +35,10 @@ riff system install [flags] ### Options ``` - --force force the install of components without getting any prompts - -h, --help help for install - --node-port whether to use NodePort instead of LoadBalancer for ingress gateways + --force force the install of components without getting any prompts + -h, --help help for install + -m, --manifest string file path of a manifest file referring to the YAML files to be applied + --node-port whether to use NodePort instead of LoadBalancer for ingress gateways ``` ### Options inherited from parent commands diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 000000000..800c3fa08 --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,8 @@ +version: 0.1 +istio: +- https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-crds.yaml +- https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-main.yaml +knative: +- https://storage.googleapis.com/knative-releases/serving/previous/v20180809-6b01d8e/release-no-mon.yaml +- https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release.yaml +- https://storage.googleapis.com/knative-releases/eventing/previous/v20180809-34ab480/release-clusterbus-stub.yaml diff --git a/pkg/core/fixtures/invalid.yaml b/pkg/core/fixtures/invalid.yaml new file mode 100644 index 000000000..9c558e357 --- /dev/null +++ b/pkg/core/fixtures/invalid.yaml @@ -0,0 +1 @@ +. diff --git a/pkg/core/fixtures/noistio.yaml b/pkg/core/fixtures/noistio.yaml new file mode 100644 index 000000000..9788f3bf7 --- /dev/null +++ b/pkg/core/fixtures/noistio.yaml @@ -0,0 +1,3 @@ +version: 0.1 +knative: + - serving-release diff --git a/pkg/core/fixtures/noknative.yaml b/pkg/core/fixtures/noknative.yaml new file mode 100644 index 000000000..68cb3cb5f --- /dev/null +++ b/pkg/core/fixtures/noknative.yaml @@ -0,0 +1,4 @@ +version: 0.1 +istio: + - istio-crds + - istio-release diff --git a/pkg/core/fixtures/valid.yaml b/pkg/core/fixtures/valid.yaml new file mode 100644 index 000000000..c70f4585c --- /dev/null +++ b/pkg/core/fixtures/valid.yaml @@ -0,0 +1,8 @@ +version: 0.1 +istio: + - istio-crds + - istio-release +knative: + - serving-release + - eventing-release + - stub-bus-release diff --git a/pkg/core/fixtures/wrongversion.yaml b/pkg/core/fixtures/wrongversion.yaml new file mode 100644 index 000000000..a619aaa5b --- /dev/null +++ b/pkg/core/fixtures/wrongversion.yaml @@ -0,0 +1 @@ +version: 0.0 diff --git a/pkg/core/manifest.go b/pkg/core/manifest.go new file mode 100644 index 000000000..739cf4b58 --- /dev/null +++ b/pkg/core/manifest.go @@ -0,0 +1,57 @@ +/* + * Copyright 2018 The original author or 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. + * + */ + +package core + +import ( + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" +) + +const MANIFEST_VERSION = "0.1" + +// Manifest defines the location of YAML files for system components. +type Manifest struct { + Version string + Istio []string + Knative []string +} + +func NewManifest(path string) (*Manifest, error) { + var m Manifest + yamlFile, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("Error reading manifest file: %v", err) + } + + err = yaml.Unmarshal(yamlFile, &m) + if err != nil { + return nil, fmt.Errorf("Error parsing manifest file: %v", err) + } + + if m.Version != MANIFEST_VERSION { + return nil, fmt.Errorf("Manifest has unsupported version: %s", m.Version) + } + + if m.Istio == nil || + m.Knative == nil { + return nil, fmt.Errorf("Manifest is incomplete: %#v", m) + } + + return &m, nil +} diff --git a/pkg/core/manifest_test.go b/pkg/core/manifest_test.go new file mode 100644 index 000000000..5b0cecccf --- /dev/null +++ b/pkg/core/manifest_test.go @@ -0,0 +1,112 @@ +/* + * Copyright 2018 The original author or 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. + * + */ + +package core_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/projectriff/riff/pkg/core" +) + +var _ = Describe("Manifest", func() { + Describe("NewManifest", func() { + + var ( + manifestPath string + manifest *core.Manifest + err error + ) + + JustBeforeEach(func() { + manifest, err = core.NewManifest(manifestPath) + }) + + Context("when an invalid path is provided", func() { + BeforeEach(func() { + manifestPath = "" + }) + + It("should return a suitable error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(HavePrefix("Error reading manifest file:")) + }) + }) + + Context("when the file contains invalid YAML", func() { + BeforeEach(func() { + manifestPath = "./fixtures/invalid.yaml" + }) + + It("should return a suitable error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(HavePrefix("Error parsing manifest file:")) + }) + }) + + Context("when the manifest has the wrong version", func() { + BeforeEach(func() { + manifestPath = "./fixtures/wrongversion.yaml" + }) + + It("should return a suitable error", func() { + Expect(err).To(MatchError("Manifest has unsupported version: 0.0")) + }) + }) + + Context("when the manifest does not specify the istio array", func() { + BeforeEach(func() { + manifestPath = "./fixtures/noistio.yaml" + }) + + It("should return a suitable error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(HavePrefix("Manifest is incomplete:")) + }) + }) + + Context("when the manifest does not specify the knative array", func() { + BeforeEach(func() { + manifestPath = "./fixtures/noknative.yaml" + }) + + It("should return a suitable error", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(HavePrefix("Manifest is incomplete:")) + }) + }) + + Context("when the manifest is valid", func() { + BeforeEach(func() { + manifestPath = "./fixtures/valid.yaml" + }) + + It("should return with no error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("should parse the istio array", func() { + Expect(manifest.Istio).To(ConsistOf("istio-crds", "istio-release")) + }) + + It("should parse the Knative array", func() { + Expect(manifest.Knative).To(ConsistOf("serving-release", "eventing-release", "stub-bus-release")) + }) + }) + }) + +}) diff --git a/pkg/core/mocks/Client.go b/pkg/core/mocks/Client.go index ee55701f6..990a87c83 100644 --- a/pkg/core/mocks/Client.go +++ b/pkg/core/mocks/Client.go @@ -1,5 +1,4 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. - package mocks import core "github.com/projectriff/riff/pkg/core" diff --git a/pkg/core/sample_manifest_test.go b/pkg/core/sample_manifest_test.go new file mode 100644 index 000000000..7ffa629cf --- /dev/null +++ b/pkg/core/sample_manifest_test.go @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The original author or 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. + * + */ + +package core + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("SampleManifest: manifest.yaml in root of this repository", func() { + It("should match the default manifest defined in the code", func() { + manifest, err := NewManifest("../../manifest.yaml") + Expect(err).NotTo(HaveOccurred()) + Expect(manifest).To(Equal(defaultManifest())) + }) +}) diff --git a/pkg/core/system.go b/pkg/core/system.go index b3d0bc5d0..7a94a8c51 100644 --- a/pkg/core/system.go +++ b/pkg/core/system.go @@ -31,7 +31,7 @@ import ( ) const ( - istioNamespace = "istio-system" + istioNamespace = "istio-system" istioCrds = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-crds.yaml" istioRelease = "https://storage.googleapis.com/riff-releases/istio/istio-1.0.0-riff-main.yaml" servingRelease = "https://storage.googleapis.com/knative-releases/serving/previous/v20180809-6b01d8e/release-no-mon.yaml" @@ -40,6 +40,7 @@ const ( ) type SystemInstallOptions struct { + Manifest string NodePort bool Force bool } @@ -55,8 +56,21 @@ var ( ) func (kc *kubectlClient) SystemInstall(options SystemInstallOptions) (bool, error) { + var ( + manifest *Manifest + err error + ) - err := ensureNotTerminating(kc, allNameSpaces, "Please try again later.") + if options.Manifest != "" { + manifest, err = NewManifest(options.Manifest) + if err != nil { + return false, err + } + } else { + manifest = defaultManifest() + } + + err = ensureNotTerminating(kc, allNameSpaces, "Please try again later.") if err != nil { return false, err } @@ -64,40 +78,16 @@ func (kc *kubectlClient) SystemInstall(options SystemInstallOptions) (bool, erro istioStatus, err := getNamespaceStatus(kc, istioNamespace) if istioStatus == "'NotFound'" { fmt.Print("Installing Istio components\n") - err = applyResources(kc, istioCrds) - if err != nil { - return false, err - } - time.Sleep(5 * time.Second) // wait for them to get created - istioYaml, err := loadRelease(istioRelease) - if err != nil { - return false, err - } - if options.NodePort { - istioYaml = bytes.Replace(istioYaml, []byte("LoadBalancer"), []byte("NodePort"), -1) - } - fmt.Printf("Applying resources defined in: %s\n", istioRelease) - istioLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &istioYaml) - if err != nil { - fmt.Printf("%s\n", istioLog) - if strings.Contains(istioLog, "forbidden") { - fmt.Print(`It looks like you don't have cluster-admin permissions. - -To fix this you need to: - 1. Delete he current failed installation using: - riff system uninstall --istio --force - 2. Give the user account used for installation cluster-admin permissions, you can use the following command: - kubectl create clusterrolebinding cluster-admin-binding \ - --clusterrole=cluster-admin \ - --user= - 3. Re-install riff - -`) + for i, release := range manifest.Istio { + if i > 0 { + time.Sleep(5 * time.Second) // wait for previous resources to be created + } + err = kc.applyRelease(release, options) + if err != nil { + return false, err } - return false, err } - - fmt.Print("Istio for riff installed\n\n") + fmt.Print("Istio components installed\n\n") } else { if !options.Force { answer, err := confirm("Istio is already installed, do you want to install the Knative components for riff?") @@ -116,27 +106,53 @@ To fix this you need to: } fmt.Print("Installing Knative components\n") + for _, release := range manifest.Knative { + err = kc.applyRelease(release, options) + if err != nil { + return false, err + } + } + fmt.Print("Knative components installed\n\n") + return true, nil +} - servingYaml, err := loadRelease(servingRelease) +func defaultManifest() *Manifest { + return &Manifest{ + Version: MANIFEST_VERSION, + Istio: []string{istioCrds, istioRelease}, + Knative: []string{servingRelease, eventingRelease, stubBusRelease}, + } +} + +func (kc *kubectlClient) applyRelease(release string, options SystemInstallOptions) error { + yaml, err := loadRelease(release) if err != nil { - return false, err + return err } if options.NodePort { - servingYaml = bytes.Replace(servingYaml, []byte("LoadBalancer"), []byte("NodePort"), -1) + yaml = bytes.Replace(yaml, []byte("type: LoadBalancer"), []byte("type: NodePort"), -1) } - fmt.Printf("Applying resources defined in: %s\n", servingRelease) - servingLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &servingYaml) + fmt.Printf("Applying resources defined in: %s\n", release) + istioLog, err := kc.kubeCtl.ExecStdin([]string{"apply", "-f", "-"}, &yaml) if err != nil { - fmt.Printf("%s\n", servingLog) - return false, err - } - - applyResources(kc, eventingRelease) + fmt.Printf("%s\n", istioLog) + if strings.Contains(istioLog, "forbidden") { + fmt.Print(`It looks like you don't have cluster-admin permissions. - applyResources(kc, stubBusRelease) +To fix this you need to: + 1. Delete he current failed installation using: + riff system uninstall --istio --force + 2. Give the user account used for installation cluster-admin permissions, you can use the following command: + kubectl create clusterrolebinding cluster-admin-binding \ + --clusterrole=cluster-admin \ + --user= + 3. Re-install riff - fmt.Print("Knative for riff installed\n\n") - return true, nil +`) + } + return err + } + return nil } func (kc *kubectlClient) SystemUninstall(options SystemUninstallOptions) (bool, error) { @@ -236,7 +252,7 @@ func resolveReleaseURLs(filename string) (url.URL, error) { if u.Scheme == "http" || u.Scheme == "https" { return *u, nil } - return *u, fmt.Errorf("filename must be file, http or https, got %s", u.Scheme) + return *u, fmt.Errorf("filename must be http or https, got %s", u.Scheme) } func loadRelease(release string) ([]byte, error) {