diff --git a/Makefile b/Makefile index 34a76bb4f594..64a97a8bdba5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_DIR=$(CURDIR)/build COVERAGE_DIR=$(BUILD_DIR)/coverage -BEATS?=auditbeat filebeat heartbeat journalbeat metricbeat packetbeat winlogbeat x-pack/functionbeat +BEATS?=auditbeat filebeat heartbeat journalbeat metricbeat packetbeat winlogbeat x-pack/functionbeat x-pack/agent PROJECTS=libbeat $(BEATS) PROJECTS_ENV=libbeat filebeat metricbeat PYTHON_ENV?=$(BUILD_DIR)/python-env diff --git a/dev-tools/mage/crossbuild.go b/dev-tools/mage/crossbuild.go index 1d8fb72a3377..2c4b302c0673 100644 --- a/dev-tools/mage/crossbuild.go +++ b/dev-tools/mage/crossbuild.go @@ -268,6 +268,7 @@ func (b GolangCrossBuilder) Build() error { "--rm", "--env", "MAGEFILE_VERBOSE="+verbose, "--env", "MAGEFILE_TIMEOUT="+EnvOr("MAGEFILE_TIMEOUT", ""), + "--env", fmt.Sprintf("SNAPSHOT=%v", Snapshot), "-v", repoInfo.RootDir+":"+mountPoint, "-w", workDir, image, diff --git a/dev-tools/mage/dmgbuilder.go b/dev-tools/mage/dmgbuilder.go index 8c7df259b237..c04a5eea6f79 100644 --- a/dev-tools/mage/dmgbuilder.go +++ b/dev-tools/mage/dmgbuilder.go @@ -244,6 +244,7 @@ func (b *dmgBuilder) buildDMG() error { "create", "-volname", b.MustExpand("{{.BeatName | title}} {{.Version}}{{if .Snapshot}}-SNAPSHOT{{end}}"), "-srcfolder", b.dmgDir, + "-size", "500m", // allowing extra space "-ov", createDir(dmgFile), } diff --git a/dev-tools/mage/dockerbuilder.go b/dev-tools/mage/dockerbuilder.go index 046a476957bb..adc30aabfd75 100644 --- a/dev-tools/mage/dockerbuilder.go +++ b/dev-tools/mage/dockerbuilder.go @@ -120,8 +120,8 @@ func (b *dockerBuilder) prepareBuild() error { "ModulesDirs": b.modulesDirs(), } - return filepath.Walk(templatesDir, func(path string, info os.FileInfo, _ error) error { - if !info.IsDir() { + err = filepath.Walk(templatesDir, func(path string, info os.FileInfo, _ error) error { + if !info.IsDir() && !isDockerFile(path) { target := strings.TrimSuffix( filepath.Join(b.buildDir, filepath.Base(path)), ".tmpl", @@ -134,6 +134,52 @@ func (b *dockerBuilder) prepareBuild() error { } return nil }) + + if err != nil { + return err + } + + return b.expandDockerfile(templatesDir, data) +} + +func isDockerFile(path string) bool { + path = filepath.Base(path) + return strings.HasPrefix(path, "Dockerfile") || strings.HasPrefix(path, "docker-entrypoint") +} + +func (b *dockerBuilder) expandDockerfile(templatesDir string, data map[string]interface{}) error { + // has specific dockerfile + dockerfile := fmt.Sprintf("Dockerfile.%s.tmpl", b.imageName) + _, err := os.Stat(filepath.Join(templatesDir, dockerfile)) + if err != nil { + // specific missing fallback to generic + dockerfile = "Dockerfile.tmpl" + } + + entrypoint := fmt.Sprintf("docker-entrypoint.%s.tmpl", b.imageName) + _, err = os.Stat(filepath.Join(templatesDir, entrypoint)) + if err != nil { + // specific missing fallback to generic + entrypoint = "docker-entrypoint.tmpl" + } + + type fileExpansion struct { + source string + target string + } + for _, file := range []fileExpansion{{dockerfile, "Dockerfile.tmpl"}, {entrypoint, "docker-entrypoint.tmpl"}} { + target := strings.TrimSuffix( + filepath.Join(b.buildDir, file.target), + ".tmpl", + ) + path := filepath.Join(templatesDir, file.source) + err = b.ExpandFile(path, target, data) + if err != nil { + return errors.Wrapf(err, "expanding template '%s' to '%s'", path, target) + } + } + + return nil } func (b *dockerBuilder) dockerBuild() (string, error) { diff --git a/dev-tools/packaging/packages.yml b/dev-tools/packaging/packages.yml index 990751b3e789..3cda43b0f2cb 100644 --- a/dev-tools/packaging/packages.yml +++ b/dev-tools/packaging/packages.yml @@ -54,11 +54,11 @@ shared: /etc/init.d/{{.BeatServiceName}}: template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/{{.PackageType}}/init.sh.tmpl' mode: 0755 - /etc/{{.BeatName}}/beats/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: - source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + /etc/{{.BeatName}}/data/downloads/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: + source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 - /etc/{{.BeatName}}/beats/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: - source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + /etc/{{.BeatName}}/data/downloads/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: + source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 @@ -97,11 +97,11 @@ shared: source: 'agent.yml' mode: 0600 config: true - /etc/{{.BeatName}}/beats/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: - source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + /etc/{{.BeatName}}/data/downloads/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: + source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 - /etc/{{.BeatName}}/beats/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: - source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + /etc/{{.BeatName}}/data/downloads/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz: + source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 - &agent_binary_files @@ -131,11 +131,11 @@ shared: <<: *common files: <<: *agent_binary_files - 'beats/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz': - source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + 'data/downloads/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz': + source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 - 'beats/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz': - source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' + 'data/downloads/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.tar.gz': + source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.tar.gz' mode: 0644 # Binary package spec (zip for windows) for community beats. @@ -149,18 +149,18 @@ shared: uninstall-service-{{.BeatName}}.ps1: template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/windows/uninstall-service.ps1.tmpl' mode: 0755 - 'beats/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip': - source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip' + 'data/downloads/filebeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip': + source: '{{ elastic_beats_dir }}/x-pack/filebeat/build/distributions/filebeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.zip' mode: 0644 - 'beats/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip': - source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip' + 'data/downloads/metricbeat-{{ beat_version }}-{{.GOOS}}-{{.AgentArchName}}.zip': + source: '{{ elastic_beats_dir }}/x-pack/metricbeat/build/distributions/metricbeat-{{ beat_version }}{{if .Snapshot}}-SNAPSHOT{{end}}-{{.GOOS}}-{{.AgentArchName}}.zip' mode: 0644 - &agent_docker_spec <<: *agent_binary_spec extra_vars: from: 'centos:7' - user: '{{ .BeatName }}' + user: 'root' linux_capabilities: '' files: 'agent.yml': diff --git a/dev-tools/packaging/templates/docker/Dockerfile.agent.tmpl b/dev-tools/packaging/templates/docker/Dockerfile.agent.tmpl new file mode 100644 index 000000000000..c85099c54324 --- /dev/null +++ b/dev-tools/packaging/templates/docker/Dockerfile.agent.tmpl @@ -0,0 +1,52 @@ +{{- $beatHome := printf "%s/%s" "/usr/share" .BeatName }} +{{- $beatBinary := printf "%s/%s" $beatHome .BeatName }} +{{- $repoInfo := repo }} + +FROM {{ .from }} + +LABEL \ + org.label-schema.build-date="{{ date }}" \ + org.label-schema.schema-version="1.0" \ + org.label-schema.vendor="{{ .BeatVendor }}" \ + org.label-schema.license="{{ .License }}" \ + org.label-schema.name="{{ .BeatName }}" \ + org.label-schema.version="{{ beat_version }}" \ + org.label-schema.url="{{ .BeatURL }}" \ + org.label-schema.vcs-url="{{ $repoInfo.RootImportPath }}" \ + org.label-schema.vcs-ref="{{ commit }}" \ + license="{{ .License }}" \ + description="{{ .BeatDescription }}" + +ENV ELASTIC_CONTAINER "true" +ENV PATH={{ $beatHome }}:$PATH + +COPY beat {{ $beatHome }} +COPY docker-entrypoint /usr/local/bin/docker-entrypoint +RUN chmod 755 /usr/local/bin/docker-entrypoint + +RUN groupadd --gid 1000 {{ .BeatName }} + +RUN mkdir -p {{ $beatHome }}/data {{ $beatHome }}/logs && \ + chown -R root:{{ .BeatName }} {{ $beatHome }} && \ + find {{ $beatHome }} -type d -exec chmod 0750 {} \; && \ + find {{ $beatHome }} -type f -exec chmod 0640 {} \; && \ + chmod 0750 {{ $beatBinary }} && \ +{{- if .linux_capabilities }} + setcap {{ .linux_capabilities }} {{ $beatBinary }} && \ +{{- end }} +{{- range $i, $modulesd := .ModulesDirs }} + chmod 0770 {{ $beatHome}}/{{ $modulesd }} && \ +{{- end }} + chmod 0770 {{ $beatHome }}/data {{ $beatHome }}/logs + +{{- if ne .user "root" }} +RUN useradd -M --uid 1000 --gid 1000 --home {{ $beatHome }} {{ .user }} +{{- end }} +USER {{ .user }} + +{{- range $i, $port := .ExposePorts }} +EXPOSE {{ $port }} +{{- end }} + +WORKDIR {{ $beatHome }} +ENTRYPOINT ["/usr/local/bin/docker-entrypoint"] diff --git a/dev-tools/packaging/templates/docker/docker-entrypoint.agent.tmpl b/dev-tools/packaging/templates/docker/docker-entrypoint.agent.tmpl new file mode 100644 index 000000000000..29ed2a60beb1 --- /dev/null +++ b/dev-tools/packaging/templates/docker/docker-entrypoint.agent.tmpl @@ -0,0 +1,55 @@ +#!/bin/bash + +set -eo pipefail + +# Environment variables used +# FLEET_ADMIN_PASSWORD - used for new fleet user [elastic] +# FLEET_ADMIN_USERNAME - used for new fleet user [changeme] +# FLEET_CONFIG_ID - config related to new token [defaul] +# FLEET_ENROLLMENT_TOKEN - existing enrollment token to be used for enroll +# FLEET_ENROLL - if set to 1 enroll will be performed +# FLEET_SETUP - if set to 1 fleet setup will be performed +# FLEET_TOKEN_NAME - token name for a token to be created +# KIBANA_HOST - actual kibana host [http://localhost:5601] +# KIBANA_PASSWORD - password for accessing kibana API [changeme] +# KIBANA_USERNAME - username for accessing kibana API [elastic] + +function setup(){ + curl -X POST ${KIBANA_HOST:-http://localhost:5601}/api/ingest_manager/setup -H 'kbn-xsrf: true' -u ${KIBANA_USERNAME:-elastic}:${KIBANA_PASSWORD:-changeme} + curl -X POST ${KIBANA_HOST:-http://localhost:5601}/api/ingest_manager/fleet/setup \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: true' \ + -d '{"admin_username":"'"${FLEET_ADMIN_USERNAME:-elastic}"'","admin_password":"'"${FLEET_ADMIN_PASSWORD:-changeme}"'"}' \ + -u ${KIBANA_USERNAME:-elastic}:${KIBANA_PASSWORD:-changeme} +} + +function enroll(){ + local enrollResp + local apiKey + + if [[ -n "${FLEET_ENROLLMENT_TOKEN}" ]] && [[ ${FLEET_ENROLLMENT_TOKEN} == 1 ]]; then + apikey = "${FLEET_ENROLLMENT_TOKEN}" + else + enrollResp=$(curl -X POST ${KIBANA_HOST:-http://localhost:5601}/api/ingest_manager/fleet/enrollment-api-keys \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: true' \ + -u ${KIBANA_USERNAME:-elastic}:${KIBANA_PASSWORD:-changeme} \ + -d '{"name":"'"${FLEET_TOKEN_NAME:-demotoken}"'","config_id":"'"${FLEET_CONFIG_ID:-default}"'"}') + + local exitCode=$? + if [ $exitCode -ne 0 ]; then + exit $exitCode + fi + + apikey=$(echo $enrollResp | jq -r '.item.api_key') + fi + + ./{{ .BeatName }} enroll ${KIBANA_HOST:-http://localhost:5601} $apikey -f +} +yum install -y epel-release +yum install -y jq + +if [[ -n "${FLEET_SETUP}" ]] && [[ ${FLEET_SETUP} == 1 ]]; then setup; fi +if [[ -n "${FLEET_ENROLL}" ]] && [[ ${FLEET_ENROLL} == 1 ]]; then enroll; fi + +exec {{ .BeatName }} run "$@" diff --git a/x-pack/agent/CHANGELOG.asciidoc b/x-pack/agent/CHANGELOG.asciidoc new file mode 100644 index 000000000000..1b19ceb6484e --- /dev/null +++ b/x-pack/agent/CHANGELOG.asciidoc @@ -0,0 +1,21 @@ +// Use these for links to issue and pulls. Note issues and pulls redirect one to +// each other on Github, so don't worry too much on using the right prefix. +:issue: https://github.com/elastic/beats/issues/ +:pull: https://github.com/elastic/beats/pull/ + + +[[release-notes-8.0.0]] +=== Agent version 8.0.0 + + +==== Breaking changes + +==== Bugfixes + +- Fixed tests on windows {pull}16922[16922] +- Fixed installers for SNAPSHOTs and windows {pull}17077[17077] + +==== New features + +- Generate index name in a format type-dataset-namespace {pull}16903[16903] +- OS agnostic default configuration {pull}17016[17016] diff --git a/x-pack/agent/_meta/agent.docker.yml b/x-pack/agent/_meta/agent.docker.yml index 3a900a1927f2..91eb2c21d81f 100644 --- a/x-pack/agent/_meta/agent.docker.yml +++ b/x-pack/agent/_meta/agent.docker.yml @@ -4,9 +4,9 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] - username: elastic - password: changeme + hosts: '${ELASTICSEARCH_HOSTS:http://elasticsearch:9200}' + username: '${ELASTICSEARCH_USERNAME:elastic}' + password: '${ELASTICSEARCH_PASSWORD:changeme}' streams: - type: event/file @@ -44,15 +44,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/_meta/agent.fleet.yml b/x-pack/agent/_meta/agent.fleet.yml index 3157dc31c16e..883f1c35835f 100644 --- a/x-pack/agent/_meta/agent.fleet.yml +++ b/x-pack/agent/_meta/agent.fleet.yml @@ -10,15 +10,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/_meta/agent.yml b/x-pack/agent/_meta/agent.yml index 00b16718a3c7..b9e6b3336cf6 100644 --- a/x-pack/agent/_meta/agent.yml +++ b/x-pack/agent/_meta/agent.yml @@ -4,7 +4,7 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] + hosts: [127.0.0.1:9200] username: elastic password: changeme @@ -45,15 +45,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/_meta/common.p2.yml b/x-pack/agent/_meta/common.p2.yml index 5a75ec2fc60a..30dd85882620 100644 --- a/x-pack/agent/_meta/common.p2.yml +++ b/x-pack/agent/_meta/common.p2.yml @@ -4,7 +4,7 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] + hosts: [127.0.0.1:9200] username: elastic password: changeme @@ -70,15 +70,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/_meta/common.reference.p2.yml b/x-pack/agent/_meta/common.reference.p2.yml index 48b735726217..e06492e3a857 100644 --- a/x-pack/agent/_meta/common.reference.p2.yml +++ b/x-pack/agent/_meta/common.reference.p2.yml @@ -4,7 +4,7 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] + hosts: [127.0.0.1:9200] username: elastic password: changeme @@ -70,15 +70,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/agent.docker.yml b/x-pack/agent/agent.docker.yml index 3a900a1927f2..91eb2c21d81f 100644 --- a/x-pack/agent/agent.docker.yml +++ b/x-pack/agent/agent.docker.yml @@ -4,9 +4,9 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] - username: elastic - password: changeme + hosts: '${ELASTICSEARCH_HOSTS:http://elasticsearch:9200}' + username: '${ELASTICSEARCH_USERNAME:elastic}' + password: '${ELASTICSEARCH_PASSWORD:changeme}' streams: - type: event/file @@ -44,15 +44,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/agent.reference.yml b/x-pack/agent/agent.reference.yml index dc425dc00b5b..8fd8b0fe6525 100644 --- a/x-pack/agent/agent.reference.yml +++ b/x-pack/agent/agent.reference.yml @@ -9,7 +9,7 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] + hosts: [127.0.0.1:9200] username: elastic password: changeme @@ -75,15 +75,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/agent.yml b/x-pack/agent/agent.yml index 07a58aed3691..23e6e3e16786 100644 --- a/x-pack/agent/agent.yml +++ b/x-pack/agent/agent.yml @@ -9,7 +9,7 @@ outputs: default: type: elasticsearch - hosts: [127.0.0.1:9200, 127.0.0.1:9300] + hosts: [127.0.0.1:9200] username: elastic password: changeme @@ -75,15 +75,15 @@ download: # e.g /windows-x86.zip sourceURI: "https://artifacts.elastic.co/downloads/beats/" # path to the directory containing downloaded packages - target_directory: "/home/elastic/downloads" + target_directory: "${path.data}/downloads" # timeout for downloading package timeout: 30s # file path to a public key used for verifying downloaded artifacts # if not file is present agent will try to load public key from elastic.co website. - pgpfile: "/home/elastic/elastic.pgp" + pgpfile: "${path.data}/elastic.pgp" # install_path describes the location of installed packages/programs. It is also used # for reading program specifications. - install_path: "/home/elastic/install" + install_path: "${path.data}/install" process: # minimal port number for spawned processes diff --git a/x-pack/agent/magefile.go b/x-pack/agent/magefile.go index d653b3bdc572..a9934e941a18 100644 --- a/x-pack/agent/magefile.go +++ b/x-pack/agent/magefile.go @@ -36,6 +36,7 @@ const ( goLicenserRepo = "github.com/elastic/go-licenser" buildDir = "build" metaDir = "_meta" + snapshotEnv = "SNAPSHOT" ) // Aliases for commands required by master makefile @@ -101,6 +102,7 @@ func (Build) GenerateConfig() error { func GolangCrossBuildOSS() error { params := devtools.DefaultGolangCrossBuildArgs() params.InputFiles = []string{"cmd/agent/agent.go"} + params.LDFlags = flagsSet() return devtools.GolangCrossBuild(params) } @@ -110,6 +112,7 @@ func GolangCrossBuild() error { params := devtools.DefaultGolangCrossBuildArgs() params.InputFiles = []string{"cmd/agent/agent.go"} params.OutputDir = "build/golang-crossbuild" + params.LDFlags = flagsSet() if err := devtools.GolangCrossBuild(params); err != nil { return err } @@ -210,7 +213,7 @@ func (Check) License() error { ) } -// Changes run git status --porcelain and return an error if we have changes or uncommited files. +// Changes run git status --porcelain and return an error if we have changes or uncommitted files. func (Check) Changes() error { out, err := sh.Output("git", "status", "--porcelain") if err != nil { @@ -354,14 +357,19 @@ func commitID() string { } func flags() string { + return strings.Join(flagsSet(), " ") +} + +func flagsSet() []string { ts := time.Now().Format(time.RFC3339) commitID := commitID() + isSnapshot, _ := os.LookupEnv(snapshotEnv) - return fmt.Sprintf( - `-X "github.com/elastic/beats/v7/x-pack/agent/pkg/release.buildTime=%s" -X "github.com/elastic/beats/v7/x-pack/agent/pkg/release.commit=%s"`, - ts, - commitID, - ) + return []string{ + fmt.Sprintf(`-X "github.com/elastic/beats/v7/x-pack/agent/pkg/release.buildTime=%s"`, ts), + fmt.Sprintf(`-X "github.com/elastic/beats/v7/x-pack/agent/pkg/release.commit=%s"`, commitID), + fmt.Sprintf(` -X "github.com/elastic/beats/v7/x-pack/agent/pkg/release.snapshot=%s"`, isSnapshot), + } } // Update is an alias for executing fields, dashboards, config, includes. diff --git a/x-pack/agent/pkg/agent/application/configuration_embed.go b/x-pack/agent/pkg/agent/application/configuration_embed.go index b87532e34ff6..2276d1d32761 100644 --- a/x-pack/agent/pkg/agent/application/configuration_embed.go +++ b/x-pack/agent/pkg/agent/application/configuration_embed.go @@ -15,7 +15,7 @@ var DefaultAgentFleetConfig []byte func init() { // Packed File // _meta/agent.fleet.yml - unpacked := packer.MustUnpack("eJyMld2OozgWx+/3MfoBZvkoWs1KcxGnCmM60Buo2Ng3K2ynDImdoEmAwGrffWVIpapnZldzEUUCcz7+53f+/veXf5n9tfp7pfan6y9ver+//jIa/eUfX9DkhP98/Ws/9CI1N3ikZFB/9ZvHb3s+oJdE0zKfENQTglnPDWuZj0dGtkqS4MhKpHJz06zcXlCse1mAAyVPikFsKhJoBN1e+FslPOxIqDvmp9/RCIww4RXF+ZkVYFORpObmReHdsUPRRwxZgiMtc23PVWWqmNEXVgCHj+DAvcBURLrC7JSEtUZxpmWct9zIyZ6n5dbmqe17bkIHxZkrYtCLU67ZGjT7AkAOsZbrp++cRB0lUnOCO/l8/o7W4Mq9XP9oVg1bemvWqj1yX3YchjWbz6wOKM56WSYHVoCeNcCp4E7RMnEqwmrq59NmDUZWRm5VJlqMQHMYTRLqA4K3tvJ2Svj5KEnmyDLRCEYdW4OOErflRiju0aUvmLTcRGNF8LRuVgqNQG+MUBtfttzkvfTT6/5ZeZtT24rVWT1qwrgoXs8KmdqRMZh+NN/6n2oz+ELLzKlI2lHvW89g6HNz6ynJp423zHwzonufoJZQKQm/3WvKW2HwQcJw3BfzLBxKdFeR4IRg3ksvuHAvOlpGBIwOlRedWJlaXR1aJidW5m8M6pGRzOF+ElidN17dc7LrGbnVws9bOoYfesdpc+/dqYiruY8dBFkvGvBxxrI1goGS7Dc7S3teQn1lJHTl81mlE3jXz1Tkpj/3RAswSJJcKpKqysMBivFka2cmHFHMtDDa7Mkc/1M+fEQwGiXUhpLMEcNZoXWqKkIX3iBrOdwpy62IEy083Mk1qNny71fkdkFxbvWzPM96oRi41NxaOoLfWHlU7JT0vPjgZmOyHsVS01PWSrjrbD8CymHeCzvrJjxzz9Ub7zHbT3oGA/NX7zq2/JQ5lNwupb/owCCeqJ+01ODpvpcX7mW1hLrnzcz345vfzbUXcdIzP6l5mXboRTuLV0QX4X1Tssw0Wy3aLHommpJ8ZvzxXQEmAfGhIqylXuRUJOzE+GR5+X2dX9Ea9RUMr6wIP3TxdCf8vOZwsHs6CBMeWJlNP9SSl89sujVfg4H7iYNg4HKSaNEAw32khI3tBdYXHt/eeb1WJHgTMBxnhl5XQ/q8WnYCuvUe6qskjprfr0EnidtYP1z6zAZaym7WKk566uFJeMsOcxI9lb71ou1X9JzamMP/4PsgDK7lwnbHSvFTrE0D/AriDsVg5F6mhZ/OvL37HZ2fZUaS28Xu6RLLcpsE75762JH7jKyO4rRV3AdamGUWD+8/ZS2Di/fTAhhK9MV618zhouHbH3ZOtSMr83H/evdMiDtKkpkhBrHDysRqbP3R9uJUEM+MyDgJ5rzWs/2speamNw14YYTVktzujLGax5ne2Dl/xP1q90qS3d275lgtK7M36oUuP21trZp62lhP/cSCZXyu1T7/Q67ntLvPqJj7sb7nz7vcs2Z1Q9DVlAT2nnRQLFsOB0XNTgmDHWG0Zavn812lr6wANTvlc077jJmo5TEeWTEz/6ih9LJeksD50azeveto93n/aV+YvReh3fejqsiT4mWqqMGO9LDm/7+fYfYsmNs9sh48pXH6wXYU/mkuy25FXJcXj1pa3oCrGEHDytxnxPL4rrn1g1wzE7k83s5sptPqnvdTjtezyoiz5H7BTwKGHSOBU5HoYuuTEI92h21+6dXa3kHW+9/zixE4H2xHE4I/x9jcfQB72qnWc58PzSgJjug5fcxJTJbb1QU902GzXt3Sw+Pe+FP27DtW1gP3As1PeUvJ8PXxTv3665f//O2/AQAA//+lslh9") + unpacked := packer.MustUnpack("eJyMVV+Po74Vfe/H2A/wK3+G1VLp9xBnBmMW2IbM2NgvFbYzhsROUBMgUPW7VyZMZqYdVfsQRQJ8zz3nnnv8r2//MLtL9ddK7Y6XP171bnf5YzT629++ockJ//78ez/0JDU3eKRkUL975v7bnPboKdG0LCYE9YRg3nPDWubjkZGNkiQ4sBKpwlw1KzdnFOtebsGekgfFIDYVCTSCbi/8jRIediTUHfOzn2gERpjwguLixLYgrUhSc/Ok8MuhQ9F7DVmCAy0Lbb+rykwxo89sCxw+gj33AlMR6QrzoiSsNYpzLeOi5UZO9ntabixObd9zEzoozl0Rg14cC83WoNltAeQQa7l++MlJ1FEiNSe4k4+nn2gNLtwr9K9m1bAbt2at2gP3ZcdhWLP5m9UexXkvy2TPtqBnDXAq+KJomTgVYTX1iyldg5GVkVuViRYj0BxGk4R6j+C1rbwXJfxilCR3ZJloBKOOrUFHidtyIxT36I0XTFpuorEieFo3K4VGoFMjVOrLlpuil3522T0qLz22rVid1L0njLfb55NCpnZkDKZfzY/+U28Gn2mZOxXJOur96BkMfW6uPSXFlHq3macjWniCWkKlJPyx9FS0wuC9hOG4286zcCjRXUWCI4JFL73gzL3oYD0iYLSvvOjIyszq6tAyObKyeGVQj4zkDveTwOqcxJeBlsUpNUUtYRSm3nsdce+jaDnBvSw3iplw/Ig1Y8egpt6lZt7LjFURV3MfO7+a1ZTF2a0GZC2HL7Yvp1rPXqpRDFxqri0dwT9ZeVCyzK1HDPeRkgaPFWHBV9w+6rnMpmUN6Li/efNqK0YwCIMnRgIHweg4/8ey5XBQMk4CFBc9glb3jRIxbvjsj4vebZcdgZ/mpKSHG+Fph20fLMeB+cBU5Kq/1vD9rIByQOqk0DpTFQkmCaMzj8L5ewQLLbx8rMjs09nHHIZ7WhYt9x5mL97PQHx41xlPqQ9G7smREndKG5DI9cy55nHeo9jy3rzp3osGjIzcZ7Wcc5TwgaaeNhXJawl1z49ZZ/X87z6/5qg74Rc1h4Pd0UGYcM/KfPq1cOWzL92ar8HA/cRBMHC55dnc5it8UEsvsPO+n128eqlI8CpgOMrHk8qeV0P2uFq4uPUO6ou0vdv3a9BJ4jY2C2dfxvlAS9nN/o+Tnnp4Et5tfzmJHkrf5tDmO3rMbM1h8c7dr4tWe2FwPWfmCDpWik+10gb4FcTdTcdcCz9TNiPfso7Oz3IjyfVsd/RWCx8QnD0356mE+sJI6MrVTSurozhuFPeBFiZyKhJ299w/5i2Dt9ynW2Ao0WebW7b3RcPXez2r1wSmtWpHVhbj7nnJS4g7SpKzrcEgdliZWI2t5ywXp4J4RHGi572wuDav/byl5qrTBjwxwmpJrs6tJ2Y9plM75/e631FsM/VlyYu5VsvK/JV6ocuPmw9ew9MHL9j8nnu1z/8H6/HmRzSC7czHZp6PO7m251ZXBF1NSWDvyPtuU/OihMGOMNp6q+fzPaUvbAtqdixmTPuMmajlMR6Xfb73UHp5L0kwZ9eCfWDkWu+2YBIQ7yvCWmbvxDkfDqoiD4qXmaIGO9LDmv9/PoMYrG6FvZOCz/no1rso/BLLercirsu3915a3oCLGEHDysJnxPrxTfOsQ0+FZiZyebyZvZlNqwX3A8bzSeXEuWE/4QcBw85mZEWis+1PQjzaHbb40qu1vX9E845v8+rd29GE4Oca6ZID2NNOtZ553jWjJDigx+w+JzFZ367O6JEO6Xp1zfbgTfsvvWffsbIeuBdofixaSobv93fqzz+//fsv/wkAAP//30VSPA==") raw, ok := unpacked["_meta/agent.fleet.yml"] if !ok { // ensure we have something loaded. diff --git a/x-pack/agent/pkg/agent/operation/operator.go b/x-pack/agent/pkg/agent/operation/operator.go index 33cbea4fd154..9eee74af1cf2 100644 --- a/x-pack/agent/pkg/agent/operation/operator.go +++ b/x-pack/agent/pkg/agent/operation/operator.go @@ -224,6 +224,7 @@ func (o *Operator) runFlow(p Descriptor, operations []operation) error { continue } + o.logger.Debugf("running operation '%s' for %s.%s", op.Name(), p.BinaryName(), p.Version()) if err := op.Run(o.bgContext, app); err != nil { return err } diff --git a/x-pack/agent/pkg/agent/operation/operator_handlers.go b/x-pack/agent/pkg/agent/operation/operator_handlers.go index 2d583f8a12dd..34022763bcba 100644 --- a/x-pack/agent/pkg/agent/operation/operator_handlers.go +++ b/x-pack/agent/pkg/agent/operation/operator_handlers.go @@ -11,6 +11,7 @@ import ( "github.com/elastic/beats/v7/x-pack/agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/agent/pkg/artifact" "github.com/elastic/beats/v7/x-pack/agent/pkg/core/plugin/app" + "github.com/elastic/beats/v7/x-pack/agent/pkg/release" ) type handleFunc func(step configrequest.Step) error @@ -66,7 +67,12 @@ func getProgramFromStepWithTags(step configrequest.Step, artifactConfig *artifac return nil, nil, err } - p := app.NewDescriptor(step.Process, step.Version, artifactConfig, tags) + version := step.Version + if release.Snapshot() { + version = fmt.Sprintf("%s-SNAPSHOT", version) + } + + p := app.NewDescriptor(step.Process, version, artifactConfig, tags) return p, config, nil } diff --git a/x-pack/agent/pkg/artifact/download/fs/downloader.go b/x-pack/agent/pkg/artifact/download/fs/downloader.go index 6c208f0656b1..c17ae5dc0e36 100644 --- a/x-pack/agent/pkg/artifact/download/fs/downloader.go +++ b/x-pack/agent/pkg/artifact/download/fs/downloader.go @@ -17,7 +17,10 @@ import ( const ( packagePermissions = 0660 - beatsSubfolder = "beats" +) + +var ( + defaultDropSubdir = filepath.Join("data", "downloads") ) // Downloader is a downloader able to fetch artifacts from elastic.co web page. @@ -83,13 +86,13 @@ func (e *Downloader) download(operatingSystem, programName, version string) (str func getDropPath(cfg *artifact.Config) string { // if drop path is not provided fallback to beats subfolder if cfg == nil || cfg.DropPath == "" { - return beatsSubfolder + return defaultDropSubdir } // if droppath does not exist fallback to beats subfolder stat, err := os.Stat(cfg.DropPath) if err != nil || !stat.IsDir() { - return beatsSubfolder + return defaultDropSubdir } return cfg.DropPath diff --git a/x-pack/agent/pkg/artifact/download/fs/verifier.go b/x-pack/agent/pkg/artifact/download/fs/verifier.go index 2a16790f4457..c8f22b363288 100644 --- a/x-pack/agent/pkg/artifact/download/fs/verifier.go +++ b/x-pack/agent/pkg/artifact/download/fs/verifier.go @@ -79,7 +79,7 @@ func (v *Verifier) Verify(programName, version string) (bool, error) { func (v *Verifier) getPublicAsc(filename string) ([]byte, error) { ascFile := fmt.Sprintf("%s%s", filename, ascSuffix) - fullPath := filepath.Join(beatsSubfolder, ascFile) + fullPath := filepath.Join(defaultDropSubdir, ascFile) b, err := ioutil.ReadFile(fullPath) if err != nil { diff --git a/x-pack/agent/pkg/artifact/install/installer.go b/x-pack/agent/pkg/artifact/install/installer.go index 7ccb6321918b..b8860f6b003f 100644 --- a/x-pack/agent/pkg/artifact/install/installer.go +++ b/x-pack/agent/pkg/artifact/install/installer.go @@ -39,5 +39,6 @@ func NewInstaller(config *artifact.Config) (Installer, error) { if runtime.GOOS == "windows" { return zip.NewInstaller(config) } + return tar.NewInstaller(config) } diff --git a/x-pack/agent/pkg/artifact/install/tar/tar_installer.go b/x-pack/agent/pkg/artifact/install/tar/tar_installer.go index 998589bad8ab..1b5e28a23ee1 100644 --- a/x-pack/agent/pkg/artifact/install/tar/tar_installer.go +++ b/x-pack/agent/pkg/artifact/install/tar/tar_installer.go @@ -54,6 +54,7 @@ func unpack(r io.Reader, dir string) error { } tr := tar.NewReader(zr) + var rootDir string for { f, err := tr.Next() @@ -70,6 +71,11 @@ func unpack(r io.Reader, dir string) error { rel := filepath.FromSlash(f.Name) abs := filepath.Join(dir, rel) + // find the root dir + if currentDir := filepath.Dir(abs); rootDir == "" || len(filepath.Dir(rootDir)) > len(currentDir) { + rootDir = currentDir + } + fi := f.FileInfo() mode := fi.Mode() switch { diff --git a/x-pack/agent/pkg/artifact/install/zip/zip_installer.go b/x-pack/agent/pkg/artifact/install/zip/zip_installer.go index 6e8d36bb9b02..f71b1e6f71a9 100644 --- a/x-pack/agent/pkg/artifact/install/zip/zip_installer.go +++ b/x-pack/agent/pkg/artifact/install/zip/zip_installer.go @@ -5,11 +5,11 @@ package zip import ( + "archive/zip" "fmt" "os" "os/exec" "path/filepath" - "strings" "github.com/elastic/beats/v7/x-pack/agent/pkg/agent/errors" "github.com/elastic/beats/v7/x-pack/agent/pkg/artifact" @@ -35,37 +35,72 @@ func NewInstaller(config *artifact.Config) (*Installer, error) { // Install performs installation of program in a specific version. // It expects package to be already downloaded. func (i *Installer) Install(programName, version, installDir string) error { - if err := i.unzip(programName, version, installDir); err != nil { + artifactPath, err := artifact.GetArtifactPath(programName, version, i.config.OS(), i.config.Arch(), i.config.TargetDirectory) + if err != nil { return err } - oldPath := filepath.Join(installDir, fmt.Sprintf("%s-%s-windows", programName, version)) - newPath := filepath.Join(installDir, strings.Title(programName)) - if err := os.Rename(oldPath, newPath); err != nil { - return errors.New(err, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, newPath)) + if err := i.unzip(artifactPath, programName, version); err != nil { + return err } - return i.runInstall(programName, installDir) -} - -func (i *Installer) unzip(programName, version, installPath string) error { - artifactPath, err := artifact.GetArtifactPath(programName, version, i.config.OS(), i.config.Arch(), i.config.TargetDirectory) + rootDir, err := i.getRootDir(artifactPath) if err != nil { return err } + // if root directory is not the same as desired directory rename + // e.g contains `-windows-` or `-SNAPSHOT-` + if rootDir != installDir { + if err := os.Rename(rootDir, installDir); err != nil { + return errors.New(err, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, installDir)) + } + } + + return i.runInstall(programName, version, installDir) +} + +func (i *Installer) unzip(artifactPath, programName, version string) error { if _, err := os.Stat(artifactPath); err != nil { return errors.New(fmt.Sprintf("artifact for '%s' version '%s' could not be found at '%s'", programName, version, artifactPath), errors.TypeFilesystem, errors.M(errors.MetaKeyPath, artifactPath)) } - powershellArg := fmt.Sprintf("Expand-Archive -Path \"%s\" -DestinationPath \"%s\"", artifactPath, installPath) + powershellArg := fmt.Sprintf("Expand-Archive -LiteralPath \"%s\" -DestinationPath \"%s\"", artifactPath, i.config.InstallPath) installCmd := exec.Command("powershell", "-command", powershellArg) return installCmd.Run() } -func (i *Installer) runInstall(programName, installPath string) error { +func (i *Installer) runInstall(programName, version, installPath string) error { powershellCmd := fmt.Sprintf(powershellCmdTemplate, installPath, programName) - installCmd := exec.Command("powershell", "-command", powershellCmd) + return installCmd.Run() } + +// retrieves root directory from zip archive +func (i *Installer) getRootDir(zipPath string) (dir string, err error) { + defer func() { + if dir != "" { + dir = filepath.Join(i.config.InstallPath, dir) + } + }() + + zipReader, err := zip.OpenReader(zipPath) + if err != nil { + return "", err + } + defer zipReader.Close() + + var rootDir string + for _, f := range zipReader.File { + if filepath.Base(f.Name) == filepath.Dir(f.Name) { + return f.Name, nil + } + + if currentDir := filepath.Dir(f.Name); rootDir == "" || len(currentDir) < len(rootDir) { + rootDir = currentDir + } + } + + return rootDir, nil +} diff --git a/x-pack/agent/pkg/basecmd/version/cmd.go b/x-pack/agent/pkg/basecmd/version/cmd.go index da05cfdd4978..b2de26fd1d44 100644 --- a/x-pack/agent/pkg/basecmd/version/cmd.go +++ b/x-pack/agent/pkg/basecmd/version/cmd.go @@ -19,10 +19,15 @@ func NewCommandWithArgs(streams *cli.IOStreams) *cobra.Command { Use: "version", Short: "Display the version of the agent.", Run: func(_ *cobra.Command, _ []string) { + version := release.Version() + if release.Snapshot() { + version = version + "-SNAPSHOT" + } + fmt.Fprintf( streams.Out, "Agent version is %s (build: %s at %s)\n", - release.Version(), + version, release.Commit(), release.BuildTime(), ) diff --git a/x-pack/agent/pkg/release/version.go b/x-pack/agent/pkg/release/version.go index d0e45df3c6cf..946e608c31e0 100644 --- a/x-pack/agent/pkg/release/version.go +++ b/x-pack/agent/pkg/release/version.go @@ -4,7 +4,10 @@ package release -import "time" +import ( + "strconv" + "time" +) // version is the current version of the agent. var version = "8.0.0" @@ -18,7 +21,10 @@ var buildTime = "" // qualifier returns the version qualifier like alpha1. var qualifier = "" -// Commit returns the current build hash or unkown if it was not injected in the build process. +// snapshot is a flag marking build as a snapshot. +var snapshot = "" + +// Commit returns the current build hash or unknown if it was not injected in the build process. func Commit() string { return commit } @@ -39,3 +45,9 @@ func Version() string { } return version + "-" + qualifier } + +// Snapshot returns true if binary was built as snapshot. +func Snapshot() bool { + val, err := strconv.ParseBool(snapshot) + return err == nil && val +}