Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick #20576 to 7.x: Build docker images based on Red Hat UBI #20753

Merged
merged 1 commit into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG-developer.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ The list below covers the major changes between 7.0.0-rc2 and master only.
- Added SQL helper that can be used from any Metricbeat module {pull}18955[18955]
- Update Go version to 1.14.4. {pull}19753[19753]
- Update Go version to 1.14.7. {pull}20508[20508]
- Add packaging for docker image based on UBI minimal 8. {pull}20576[20576]
19 changes: 7 additions & 12 deletions dev-tools/mage/dockerbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,14 @@ func isDockerFile(path string) bool {
}

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"
dockerfile := "Dockerfile.tmpl"
if f, found := b.ExtraVars["dockerfile"]; found {
dockerfile = f
}

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"
entrypoint := "docker-entrypoint.tmpl"
if e, found := b.ExtraVars["docker_entrypoint"]; found {
entrypoint = e
}

type fileExpansion struct {
Expand All @@ -176,7 +171,7 @@ func (b *dockerBuilder) expandDockerfile(templatesDir string, data map[string]in
".tmpl",
)
path := filepath.Join(templatesDir, file.source)
err = b.ExpandFile(path, target, data)
err := b.ExpandFile(path, target, data)
if err != nil {
return errors.Wrapf(err, "expanding template '%s' to '%s'", path, target)
}
Expand Down
1 change: 1 addition & 0 deletions dev-tools/mage/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ var (
"repo": GetProjectRepoInfo,
"title": strings.Title,
"tolower": strings.ToLower,
"contains": strings.Contains,
}
)

Expand Down
54 changes: 50 additions & 4 deletions dev-tools/packaging/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ const (
)

var (
configFilePattern = regexp.MustCompile(`.*beat\.yml$|apm-server\.yml$`)
configFilePattern = regexp.MustCompile(`.*beat\.yml$|apm-server\.yml|elastic-agent\.yml$`)
manifestFilePattern = regexp.MustCompile(`manifest.yml`)
modulesDirPattern = regexp.MustCompile(`module/.+`)
modulesDDirPattern = regexp.MustCompile(`modules.d/$`)
modulesDFilePattern = regexp.MustCompile(`modules.d/.+`)
monitorsDFilePattern = regexp.MustCompile(`monitors.d/.+`)
systemdUnitFilePattern = regexp.MustCompile(`/lib/systemd/system/.*\.service`)

licenseFiles = []string{"LICENSE.txt", "NOTICE.txt"}
)

var (
Expand Down Expand Up @@ -122,6 +124,7 @@ func checkRPM(t *testing.T, file string) {
checkModulesPresent(t, "/usr/share", p)
checkModulesDPresent(t, "/etc/", p)
checkMonitorsDPresent(t, "/etc", p)
checkLicensesPresent(t, "/usr/share", p)
checkSystemdUnitPermissions(t, p)
ensureNoBuildIDLinks(t, p)
}
Expand All @@ -141,6 +144,7 @@ func checkDeb(t *testing.T, file string, buf *bytes.Buffer) {
checkModulesPresent(t, "./usr/share", p)
checkModulesDPresent(t, "./etc/", p)
checkMonitorsDPresent(t, "./etc/", p)
checkLicensesPresent(t, "./usr/share", p)
checkModulesOwner(t, p, true)
checkModulesPermissions(t, p)
checkSystemdUnitPermissions(t, p)
Expand All @@ -160,6 +164,7 @@ func checkTar(t *testing.T, file string) {
checkModulesDPresent(t, "", p)
checkModulesPermissions(t, p)
checkModulesOwner(t, p, true)
checkLicensesPresent(t, "", p)
}

func checkZip(t *testing.T, file string) {
Expand All @@ -174,6 +179,7 @@ func checkZip(t *testing.T, file string) {
checkModulesPresent(t, "", p)
checkModulesDPresent(t, "", p)
checkModulesPermissions(t, p)
checkLicensesPresent(t, "", p)
}

func checkDocker(t *testing.T, file string) {
Expand All @@ -190,6 +196,7 @@ func checkDocker(t *testing.T, file string) {
checkManifestPermissionsWithMode(t, p, os.FileMode(0640))
checkModulesPresent(t, "", p)
checkModulesDPresent(t, "", p)
checkLicensesPresent(t, "licenses/", p)
}

// Verify that the main configuration file is installed with a 0600 file mode.
Expand Down Expand Up @@ -373,6 +380,22 @@ func checkMonitors(t *testing.T, name, prefix string, r *regexp.Regexp, p *packa
})
}

func checkLicensesPresent(t *testing.T, prefix string, p *packageFile) {
for _, licenseFile := range licenseFiles {
t.Run("License file "+licenseFile, func(t *testing.T) {
for _, entry := range p.Contents {
if strings.HasPrefix(entry.File, prefix) && strings.HasSuffix(entry.File, "/"+licenseFile) {
return
}
}
if prefix != "" {
t.Fatalf("not found under %s", prefix)
}
t.Fatal("not found")
})
}
}

func checkDockerEntryPoint(t *testing.T, p *packageFile, info *dockerInfo) {
expectedMode := os.FileMode(0755)

Expand Down Expand Up @@ -402,7 +425,8 @@ func checkDockerLabels(t *testing.T, p *packageFile, info *dockerInfo, file stri
if vendor != "Elastic" {
return
}
t.Run(fmt.Sprintf("%s labels", p.Name), func(t *testing.T) {

t.Run(fmt.Sprintf("%s license labels", p.Name), func(t *testing.T) {
expectedLicense := "Elastic License"
ossPrefix := strings.Join([]string{
info.Config.Labels["org.label-schema.name"],
Expand All @@ -412,8 +436,24 @@ func checkDockerLabels(t *testing.T, p *packageFile, info *dockerInfo, file stri
if strings.HasPrefix(filepath.Base(file), ossPrefix) {
expectedLicense = "ASL 2.0"
}
if license, present := info.Config.Labels["license"]; !present || license != expectedLicense {
t.Errorf("unexpected license label: %s", license)
licenseLabels := []string{
"license",
"org.label-schema.license",
}
for _, licenseLabel := range licenseLabels {
if license, present := info.Config.Labels[licenseLabel]; !present || license != expectedLicense {
t.Errorf("unexpected license label %s: %s", licenseLabel, license)
}
}
})

t.Run(fmt.Sprintf("%s required labels", p.Name), func(t *testing.T) {
// From https://redhat-connect.gitbook.io/partner-guide-for-red-hat-openshift-and-container/program-on-boarding/technical-prerequisites
requiredLabels := []string{"name", "vendor", "version", "release", "summary", "description"}
for _, label := range requiredLabels {
if value, present := info.Config.Labels[label]; !present || value == "" {
t.Errorf("missing required label %s", label)
}
}
})
}
Expand Down Expand Up @@ -657,6 +697,12 @@ func readDocker(dockerFile string) (*packageFile, *dockerInfo, error) {
if strings.HasPrefix("/"+name, workingDir) || "/"+name == entrypoint {
p.Contents[name] = entry
}
// Add also licenses
for _, licenseFile := range licenseFiles {
if strings.Contains(name, licenseFile) {
p.Contents[name] = entry
}
}
}
}

Expand Down
48 changes: 48 additions & 0 deletions dev-tools/packaging/packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ shared:
/usr/share/{{.BeatName}}/LICENSE.txt:
source: '{{ repo.RootDir }}/LICENSE.txt'
mode: 0644
/usr/share/{{.BeatName}}/NOTICE.txt:
source: '{{ repo.RootDir }}/NOTICE.txt'
mode: 0644
/usr/share/{{.BeatName}}/README.md:
template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/common/README.md.tmpl'
mode: 0644
Expand Down Expand Up @@ -117,6 +120,9 @@ shared:
/Library/Application Support/{{.BeatVendor}}/{{.BeatName}}/LICENSE.txt:
source: '{{ repo.RootDir }}/LICENSE.txt'
mode: 0644
/Library/Application Support/{{.BeatVendor}}/{{.BeatName}}/NOTICE.txt:
source: '{{ repo.RootDir }}/NOTICE.txt'
mode: 0644
/Library/Application Support/{{.BeatVendor}}/{{.BeatName}}/README.md:
template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/common/README.md.tmpl'
mode: 0644
Expand Down Expand Up @@ -186,6 +192,9 @@ shared:
LICENSE.txt:
source: '{{ repo.RootDir }}/LICENSE.txt'
mode: 0644
NOTICE.txt:
source: '{{ repo.RootDir }}/NOTICE.txt'
mode: 0644
README.md:
template: '{{ elastic_beats_dir }}/dev-tools/packaging/templates/common/README.md.tmpl'
mode: 0644
Expand Down Expand Up @@ -307,6 +316,9 @@ shared:
<<: *agent_binary_spec
extra_vars:
from: 'centos:7'
buildFrom: 'centos:7'
dockerfile: 'Dockerfile.elastic-agent.tmpl'
docker_entrypoint: 'docker-entrypoint.elastic-agent.tmpl'
user: 'root'
linux_capabilities: ''
files:
Expand Down Expand Up @@ -460,6 +472,7 @@ shared:
<<: *binary_spec
extra_vars:
from: 'centos:7'
buildFrom: 'centos:7'
user: '{{ .BeatName }}'
linux_capabilities: ''
files:
Expand All @@ -468,6 +481,11 @@ shared:
mode: 0600
config: true

- &docker_ubi_spec
extra_vars:
image_name: '{{.BeatName}}-ubi8'
from: 'registry.access.redhat.com/ubi8/ubi-minimal'

- &elastic_docker_spec
extra_vars:
repository: 'docker.elastic.co/beats'
Expand Down Expand Up @@ -637,6 +655,14 @@ specs:
<<: *elastic_docker_spec
<<: *elastic_license_for_binaries

- os: linux
types: [docker]
spec:
<<: *docker_spec
<<: *docker_ubi_spec
<<: *elastic_docker_spec
<<: *elastic_license_for_binaries

# Elastic Beat with Elastic License and binary taken the current directory.
elastic_beat_xpack_reduced:
###
Expand Down Expand Up @@ -721,6 +747,17 @@ specs:
'{{.BeatName}}{{.BinaryExt}}':
source: ./{{.XPackDir}}/{{.BeatName}}/build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}}

- os: linux
types: [docker]
spec:
<<: *docker_spec
<<: *docker_ubi_spec
<<: *elastic_docker_spec
<<: *elastic_license_for_binaries
files:
'{{.BeatName}}{{.BinaryExt}}':
source: ./{{.XPackDir}}/{{.BeatName}}/build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}}

# Elastic Beat with Elastic License and binary taken from the x-pack dir.
elastic_beat_agent_binaries:
###
Expand Down Expand Up @@ -782,6 +819,17 @@ specs:
'{{.BeatName}}{{.BinaryExt}}':
source: ./build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}}

- os: linux
types: [docker]
spec:
<<: *agent_docker_spec
<<: *docker_ubi_spec
<<: *elastic_docker_spec
<<: *elastic_license_for_binaries
files:
'{{.BeatName}}{{.BinaryExt}}':
source: ./build/golang-crossbuild/{{.BeatName}}-{{.GOOS}}-{{.Platform.Arch}}{{.BinaryExt}}


# Elastic Beat with Elastic License and binary taken from the x-pack dir.
elastic_beat_agent_demo_binaries:
Expand Down
59 changes: 44 additions & 15 deletions dev-tools/packaging/templates/docker/Dockerfile.elastic-agent.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,38 @@
{{- $beatBinary := printf "%s/%s" $beatHome .BeatName }}
{{- $repoInfo := repo }}

# Prepare home in a different stage to avoid creating additional layers on
# the final image because of permission changes.
FROM {{ .buildFrom }} AS home

COPY beat {{ $beatHome }}

RUN mkdir -p {{ $beatHome }}/data {{ $beatHome }}/logs && \
chown -R root:root {{ $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

FROM {{ .from }}

{{- if contains .from "ubi-minimal" }}
RUN for iter in {1..10}; do microdnf update -y && microdnf install -y shadow-utils && microdnf clean all && exit_code=0 && break || exit_code=$? && echo "microdnf error: retry $iter in 10s" && sleep 10; done; (exit $exit_code)
RUN curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o /usr/local/bin/jq && \
chmod +x /usr/local/bin/jq
{{- else }}
# Installing jq needs to be installed after epel-release and cannot be in the same yum install command.
RUN yum -y --setopt=tsflags=nodocs update && \
yum install epel-release -y && \
yum install jq -y && \
yum clean all
{{- end }}

LABEL \
org.label-schema.build-date="{{ date }}" \
Expand All @@ -20,33 +45,37 @@ LABEL \
org.label-schema.url="{{ .BeatURL }}" \
org.label-schema.vcs-url="{{ $repoInfo.RootImportPath }}" \
org.label-schema.vcs-ref="{{ commit }}" \
io.k8s.description="{{ .BeatDescription }}" \
io.k8s.display-name="{{ .BeatName | title }} image" \
org.opencontainers.image.created="{{ date }}" \
org.opencontainers.image.licenses="{{ .License }}" \
org.opencontainers.image.title="{{ .BeatName | title }}" \
org.opencontainers.image.vendor="{{ .BeatVendor }}" \
name="{{ .BeatName }}" \
maintainer="infra@elastic.co" \
vendor="{{ .BeatVendor }}" \
version="{{ beat_version }}" \
release="1" \
url="{{ .BeatURL }}" \
summary="{{ .BeatName }}" \
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 }}
COPY --from=home {{ $beatHome }} {{ $beatHome }}

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
RUN mkdir /licenses
COPY --from=home {{ $beatHome }}/LICENSE.txt /licenses
COPY --from=home {{ $beatHome }}/NOTICE.txt /licenses

{{- if ne .user "root" }}
RUN useradd -M --uid 1000 --gid 1000 --home {{ $beatHome }} {{ .user }}
RUN groupadd --gid 1000 {{ .BeatName }}
RUN useradd -M --uid 1000 --gid 1000 --groups 0 --home {{ $beatHome }} {{ .user }}
{{- end }}
USER {{ .user }}

Expand Down
Loading