diff --git a/Dockerfile.in b/Dockerfile.in index 45a064d..94513df 100644 --- a/Dockerfile.in +++ b/Dockerfile.in @@ -3,5 +3,4 @@ FROM ARG_FROM MAINTAINER CoreOS Inc. ADD bin/docker-rootfs-ARG_ARCH.tgz / -ADD versions.yaml /versions.yaml ADD app-signing-pubkey.gpg /pubring.gpg diff --git a/Documentation/bootstrapper.md b/Documentation/bootstrapper.md index 51c707f..7e19ab8 100644 --- a/Documentation/bootstrapper.md +++ b/Documentation/bootstrapper.md @@ -31,10 +31,8 @@ The bootstrapper tries to gather state from the cluster and from a [remote bucke This is the logic flow and the information sources it queries: 1. Get cluster version from api-server `/version` endpoint 1. Iff previous step permanently failed, use `/etc/kubernetes/installer/kubelet.env` to determine the install-time kubernetes version. - 1. Get needed addon (docker) versions from `/versions.yaml` hardcoded inside tectonic-torcx container - * FUTURE - after getting rid of in-container runtime mappings instead: - * Get runtime mappings from `tectonic-torcx-runtime-mappings` ConfigMap - * Iff previous step permanently failed, use `/etc/kubernetes/installer/runtime-mappings.yaml` to determine runtime mappings + 1. Get runtime mappings from `tectonic-torcx-runtime-mappings` ConfigMap + 1. Iff previous step permanently failed, use `/etc/kubernetes/installer/runtime-mappings.yaml` to determine runtime mappings 1. Retrieve current OS version from `/usr/lib/os-release` 1. Gather next OS version (if any) from `update_engine` via DBus 1. Retrieve torcx remote manifests for both OS versions from a remote URL (see configuration notes above) diff --git a/Makefile b/Makefile index c713089..3196f71 100644 --- a/Makefile +++ b/Makefile @@ -33,10 +33,6 @@ VERSION ?= $(shell git describe --tags --always --dirty) # Multicall binaries (symlink basenames). MULTICALLS := tectonic-torcx-bootstrap tectonic-torcx-hook-pre -# The installer uses a mutable tag; tag all production -# builds with that -PRODTAG := installer-latest - ### ### These variables should not need tweaking. ### @@ -126,20 +122,8 @@ endif @docker images -q $(IMAGE):$(VERSION) > $@ -push-production: - @git describe --tags --exact-match > /dev/null # This will error if this is not a tagged commit -ifneq (,$(findstring dirty,$(VERSION))) - @echo "working tree is dirty, quitting" - @exit 1 -endif - - @docker tag $(IMAGE):$(VERSION) $(IMAGE):$(PRODTAG) - @docker push $(IMAGE):$(PRODTAG) - @echo "pushed: $(IMAGE):$(PRODTAG)" - push-name: @echo "pushed: $(IMAGE):$(VERSION)" - @echo "You probably want to update the installer's tag with make push-production" version: @echo $(VERSION) diff --git a/cli/common.go b/cli/common.go index 86d0d47..3b594f3 100644 --- a/cli/common.go +++ b/cli/common.go @@ -63,12 +63,14 @@ func commonFlags(f *pflag.FlagSet) { f.StringVar(&cfg.ForceKubeVersion, "force-kube-version", "", "force a kubernetes version, rather than determining from the apiserver") f.BoolVar(&cfg.NoVerifySig, "no-verify-signatures", false, "don't gpg-verify all downloaded addons") f.StringVar(&cfg.GpgKeyringPath, "keyring", "/pubring.gpg", "path to the gpg keyring") - f.StringVar(&cfg.VersionManifestPath, "version-manifest", "/versions.yaml", "path to the version manifest file") + f.StringVar(&cfg.VersionManifestPath, "version-manifest", "", "path to the runtime-mappings manifest file") f.StringVar(&verbose, "verbose", "info", "verbosity level") } -// parseFlags parses CLI options, returning a populated configuration for the bootstrap agent -func parseFlags() (internal.Config, error) { +// parseFlags parses CLI options, returning a populated configuration for +// the bootstrap agent. It takes the path to the version manifest containing runtime +// mappings (consumed by hook logic and used as fallback by the bootstrapper). +func parseFlags(defaultRuntimeMappingsPath string) (internal.Config, error) { zero := internal.Config{} lvl, err := logrus.ParseLevel(verbose) @@ -100,7 +102,7 @@ func parseFlags() (internal.Config, error) { cfg.TorcxManifestURL = tmpl if cfg.VersionManifestPath == "" { - return zero, errors.New("version-manifest required") + cfg.VersionManifestPath = defaultRuntimeMappingsPath } return cfg, nil diff --git a/cli/tectonic-torcx-bootstrap.go b/cli/tectonic-torcx-bootstrap.go index 020bab9..5265fc8 100644 --- a/cli/tectonic-torcx-bootstrap.go +++ b/cli/tectonic-torcx-bootstrap.go @@ -43,7 +43,7 @@ func bootstrapInit() { } func runBootstrap(cmd *cobra.Command, args []string) error { - conf, err := parseFlags() + conf, err := parseFlags(internal.InstallerRuntimeMappings) if err != nil { return err } diff --git a/cli/tectonic-torcx-hook-pre.go b/cli/tectonic-torcx-hook-pre.go index 0b77260..a09ec53 100644 --- a/cli/tectonic-torcx-hook-pre.go +++ b/cli/tectonic-torcx-hook-pre.go @@ -51,7 +51,7 @@ func runHookPre(cmd *cobra.Command, args []string) error { if err == nil { logrus.AddHook(hook) } - conf, err := parseFlags() + conf, err := parseFlags(internal.CluoRuntimeMappings) if err != nil { return err } diff --git a/deploy/pre-reboot-hook.yaml b/deploy/pre-reboot-hook.yaml index c22d585..a74b981 100644 --- a/deploy/pre-reboot-hook.yaml +++ b/deploy/pre-reboot-hook.yaml @@ -58,6 +58,9 @@ spec: name: tmp - mountPath: /dev/log name: dev-log + - mountPath: /etc/runtime-mappings.yaml + name: runtime-mappings-cm + subPath: runtime-mappings.yaml env: - name: NODE valueFrom: @@ -101,4 +104,7 @@ spec: emptyDir: {} - name: dev-log hostPath: - path: /dev/log + path: /dev/log + - name: runtime-mappings-cm + configMap: + name: tectonic-torcx-runtime-mappings diff --git a/deploy/runtime-mappings.yaml b/deploy/runtime-mappings.yaml new file mode 100644 index 0000000..e6881fc --- /dev/null +++ b/deploy/runtime-mappings.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: tectonic-torcx-runtime-mappings + namespace: kube-system +data: + runtime-mappings.yaml: | + kind: VersionManifestV1 + versions: + k8s: + 1.6: + docker: [ "1.12"] + 1.7: + # Kubernetes: https://github.com/kubernetes/kubernetes/blob/v1.9.0-alpha.2/CHANGELOG-1.7.md#external-dependency-version-information + # Latest CL stable: https://tectonic-torcx.release.core-os.net/manifests/amd64-usr/1520.8.0/torcx_manifest.json + docker: [ "1.12" ] + 1.8: + # Kubernetes: https://github.com/kubernetes/kubernetes/blob/v1.9.0-alpha.2/CHANGELOG-1.8.md#external-dependency-version-information + # Latest CL stable: https://tectonic-torcx.release.core-os.net/manifests/amd64-usr/1520.8.0/torcx_manifest.json + docker: [ "17.03", "1.12"] diff --git a/internal/app.go b/internal/app.go index ff0ac06..ccbc15a 100644 --- a/internal/app.go +++ b/internal/app.go @@ -127,7 +127,7 @@ func (a *App) GatherState(localOnly bool, envPath string) error { } logrus.Infof("Detected Kubernetes version %s", a.K8sVersion) - a.DockerVersions, err = a.VersionFor("docker", a.K8sVersion) + a.DockerVersions, err = a.VersionFor(localOnly, "docker", a.K8sVersion) if err != nil { return err } diff --git a/internal/versions.go b/internal/versions.go index 05f5717..777be9a 100644 --- a/internal/versions.go +++ b/internal/versions.go @@ -21,10 +21,27 @@ import ( "github.com/coreos/go-semver/semver" "github.com/pkg/errors" + "github.com/sirupsen/logrus" yaml "gopkg.in/yaml.v2" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" ) -const KIND_VERSION_MANIFEST = "VersionManifestV1" +const ( + // CluoRuntimeMappings is the default path for hook runtime-mappings (tectonic-cluo ConfigMap) + CluoRuntimeMappings = "/etc/runtime-mappings.yaml" + // InstallerRuntimeMappings is the default path for bootstrapper runtime-mappings (installer file) + InstallerRuntimeMappings = "/etc/kubernetes/installer/runtime-mappings.yaml" + // versionManifestKind is the current type for the runtime-mappings YAML object + versionManifestKind = "VersionManifestV1" + // configMapNamespace is the default namespace for runtime-mappings ConfigMap + configMapNamespace = "tectonic-system" + // configMapName is the default name for the runtime-mappings ConfigMap + configMapName = "tectonic-torcx-runtime-mappings" + // configMapKey is the default object/key in the runtime-mappings ConfigMap + configMapKey = "runtime-mappings.yaml" +) type VersionManifest struct { Kind string `yaml:"kind"` @@ -36,8 +53,10 @@ type Dep map[string]map[string][]string // VersionFor parses the version manifest file and returns the list of preferred // package versions for a given k8s version. The returned value will never be // empty if error is nil. -func (a *App) VersionFor(name, k8sVersion string) ([]string, error) { - m, err := a.GetVersionManifest() +func (a *App) VersionFor(localOnly bool, name, k8sVersion string) ([]string, error) { + // TODO(lucab): consider caching this manifest if we grow to + // more components other than docker. + m, err := a.GetVersionManifest(localOnly) if err != nil { return nil, err } @@ -81,7 +100,7 @@ func parseVersionManifest(data []byte) (*VersionManifest, error) { return nil, errors.Wrap(err, "failed to parse version manifest") } - if m.Kind != KIND_VERSION_MANIFEST { + if m.Kind != versionManifestKind { return nil, fmt.Errorf("did not understand version kind %s", m.Kind) } @@ -89,11 +108,64 @@ func parseVersionManifest(data []byte) (*VersionManifest, error) { } // GetVersionManifest parses the version manifest file supplied by the user. -func (a *App) GetVersionManifest() (*VersionManifest, error) { - data, err := ioutil.ReadFile(a.Conf.VersionManifestPath) +func (a *App) GetVersionManifest(localOnly bool) (*VersionManifest, error) { + path := a.Conf.VersionManifestPath + if path == "" { + return nil, errors.New("missing version manifest path") + } + + // Conditionally try ConfigMap from api-server first (bootstrapper only) + if !localOnly { + logrus.Debug("Querying api-server for runtime mappings ConfigMap") + manifest, err := a.versionManifestFromAPIServer() + if err == nil { + return parseVersionManifest([]byte(manifest)) + } + logrus.Warnf("Failed to query api-server for ConfigMap: %s", err) + } + + // Source mappings from local file + data, err := ioutil.ReadFile(path) if err != nil { - return nil, errors.Wrap(err, "Failed to read version manifest") + return nil, errors.Wrapf(err, "Failed to read runtime mappings from %q", path) } return parseVersionManifest(data) } + +// versionManifestFromAPIServer connects to the APIServer and determines +// runtime mappings from the relevant ConfigMap. +func (a *App) versionManifestFromAPIServer() (string, error) { + config, err := clientcmd.BuildConfigFromFlags("", a.Conf.Kubeconfig) + if err != nil { + return "", errors.Wrap(err, "failed to build kubeconfig") + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return "", errors.Wrap(err, "failed to build kube client") + } + + var versionManifest string + err = retry(3, 10, func() error { + cmi := client.ConfigMaps(configMapNamespace) + if cmi == nil { + return errors.Errorf("nil ConfigMapInterface for namespace %s", configMapNamespace) + } + cm, e := cmi.Get(configMapName, meta_v1.GetOptions{}) + if e != nil { + return e + } + if cm == nil || cm.Data[configMapKey] == "" { + return errors.Errorf("missing entry %s/%s", configMapName, configMapKey) + } + versionManifest = cm.Data[configMapKey] + return nil + }) + if err != nil { + return "", errors.Wrapf(err, "failed to get ConfigMap %s/%s", configMapNamespace, configMapName) + } + logrus.Debugf("Got %s from ConfigMap %s/%s", configMapKey, configMapNamespace, configMapName) + + return versionManifest, nil +} diff --git a/versions.yaml b/versions.yaml deleted file mode 100644 index 920e5b7..0000000 --- a/versions.yaml +++ /dev/null @@ -1,16 +0,0 @@ -kind: VersionManifestV1 -versions: - k8s: - 1.6: - docker: [ "1.12"] - 1.7: - # Kubernetes: https://github.com/kubernetes/kubernetes/blob/v1.9.0-alpha.2/CHANGELOG-1.7.md#external-dependency-version-information - # Latest CL stable: https://tectonic-torcx.release.core-os.net/manifests/amd64-usr/1520.8.0/torcx_manifest.json - docker: [ "1.12" ] - 1.8: - # Kubernetes: https://github.com/kubernetes/kubernetes/blob/v1.9.0-alpha.2/CHANGELOG-1.8.md#external-dependency-version-information - # Latest CL stable: https://tectonic-torcx.release.core-os.net/manifests/amd64-usr/1520.8.0/torcx_manifest.json - docker: [ "17.03", "1.12"] - 1.9: - # TODO: forecast only, fix this with actual versions - docker: [ "17.03", "1.12"]