Skip to content

Commit

Permalink
Make interactive setup work properly in Docker container. (#110629)
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin authored Sep 7, 2021
1 parent 9b41b3f commit 1eed669
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ kibana_vars=(
enterpriseSearch.host
externalUrl.policy
i18n.locale
interactiveSetup.enabled
interactiveSetup.connectionCheck.interval
interpreter.enableInVisualize
kibana.autocompleteTerminateAfter
kibana.autocompleteTimeout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ COPY --from=builder --chown=1000:0 /usr/share/kibana /usr/share/kibana
WORKDIR /usr/share/kibana
RUN ln -s /usr/share/kibana /opt/kibana

{{! Please notify @elastic/kibana-security if you want to remove or change this environment variable. }}
ENV ELASTIC_CONTAINER true
ENV PATH=/usr/share/kibana/bin:$PATH

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ COPY --from=prep_files --chown=1000:0 /usr/share/kibana /usr/share/kibana
WORKDIR /usr/share/kibana
RUN ln -s /usr/share/kibana /opt/kibana

{{! Please notify @elastic/kibana-security if you want to remove or change this environment variable. }}
ENV ELASTIC_CONTAINER true
ENV PATH=/usr/share/kibana/bin:$PATH

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import dedent from 'dedent';

import { TemplateContext } from '../template_context';

// IMPORTANT: Please notify @elastic/kibana-security if you're changing any of the Docker specific
// configuration defaults. We rely on these defaults in the interactive setup mode.
function generator({ imageFlavor }: TemplateContext) {
return dedent(`
#
Expand Down
313 changes: 223 additions & 90 deletions src/plugins/interactive_setup/server/kibana_config_writer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import { KibanaConfigWriter } from './kibana_config_writer';
describe('KibanaConfigWriter', () => {
let mockFsAccess: jest.Mock;
let mockWriteFile: jest.Mock;
let mockAppendFile: jest.Mock;
let mockReadFile: jest.Mock;
let kibanaConfigWriter: KibanaConfigWriter;
beforeEach(() => {
jest.spyOn(Date, 'now').mockReturnValue(1234);

const fsMocks = jest.requireMock('fs/promises');
mockFsAccess = fsMocks.access;
mockWriteFile = fsMocks.writeFile;
mockAppendFile = fsMocks.appendFile;
mockReadFile = fsMocks.readFile;

mockReadFile.mockResolvedValue('');

kibanaConfigWriter = new KibanaConfigWriter(
'/some/path/kibana.yml',
Expand Down Expand Up @@ -69,90 +71,123 @@ describe('KibanaConfigWriter', () => {
});

describe('#writeConfig()', () => {
it('throws if cannot write CA file', async () => {
mockWriteFile.mockRejectedValue(new Error('Oh no!'));

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: '',
serviceAccountToken: { name: '', value: '' },
})
).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`);

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockAppendFile).not.toHaveBeenCalled();
});

it('throws if cannot append config to yaml file', async () => {
mockAppendFile.mockRejectedValue(new Error('Oh no!'));

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`);

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockAppendFile).toHaveBeenCalledTimes(1);
expect(mockAppendFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
describe('without existing config', () => {
beforeEach(() => {
mockReadFile.mockResolvedValue('');
});

it('throws if cannot write CA file', async () => {
mockWriteFile.mockRejectedValue(new Error('Oh no!'));

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: '',
serviceAccountToken: { name: '', value: '' },
})
).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`);

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
});

it('throws if cannot write config to yaml file', async () => {
mockWriteFile.mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error('Oh no!'));

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`);

expect(mockWriteFile).toHaveBeenCalledTimes(2);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockWriteFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.serviceAccountToken: some-value
elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt]
`
);
});

it('can successfully write CA certificate and elasticsearch config with service token', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).resolves.toBeUndefined();

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockAppendFile).toHaveBeenCalledTimes(1);
expect(mockAppendFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
);
});

it('throws if cannot read existing config', async () => {
mockReadFile.mockRejectedValue(new Error('Oh no!'));

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`);

expect(mockWriteFile).not.toHaveBeenCalled();
});

it('throws if cannot parse existing config', async () => {
mockReadFile.mockResolvedValue('foo: bar\nfoo: baz');

await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).rejects.toMatchInlineSnapshot(`
[YAMLException: duplicated mapping key at line 2, column 1:
foo: baz
^]
`);

expect(mockWriteFile).not.toHaveBeenCalled();
});

it('can successfully write CA certificate and elasticsearch config with service token', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).resolves.toBeUndefined();

expect(mockWriteFile).toHaveBeenCalledTimes(2);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockWriteFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.serviceAccountToken: some-value
elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt]
`
);
});

it('can successfully write CA certificate and elasticsearch config with credentials', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
username: 'username',
password: 'password',
})
).resolves.toBeUndefined();

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockAppendFile).toHaveBeenCalledTimes(1);
expect(mockAppendFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
);
});

it('can successfully write CA certificate and elasticsearch config with credentials', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
username: 'username',
password: 'password',
})
).resolves.toBeUndefined();

expect(mockWriteFile).toHaveBeenCalledTimes(2);
expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content');
expect(mockWriteFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
Expand All @@ -161,31 +196,129 @@ elasticsearch.username: username
elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt]
`
);
});

it('can successfully write elasticsearch config without CA certificate', async () => {
await expect(
kibanaConfigWriter.writeConfig({
host: 'some-host',
username: 'username',
password: 'password',
})
).resolves.toBeUndefined();

expect(mockWriteFile).not.toHaveBeenCalled();
expect(mockAppendFile).toHaveBeenCalledTimes(1);
expect(mockAppendFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
);
});

it('can successfully write elasticsearch config without CA certificate', async () => {
await expect(
kibanaConfigWriter.writeConfig({
host: 'some-host',
username: 'username',
password: 'password',
})
).resolves.toBeUndefined();

expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith(
'/some/path/kibana.yml',
`
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.password: password
elasticsearch.username: username
`
);
);
});
});

describe('with existing config (no conflicts)', () => {
beforeEach(() => {
mockReadFile.mockResolvedValue(
'# Default Kibana configuration for docker target\nserver.host: "0.0.0.0"\nserver.shutdownTimeout: "5s"'
);
});

it('can successfully write CA certificate and elasticsearch config', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).resolves.toBeUndefined();

expect(mockReadFile).toHaveBeenCalledTimes(1);
expect(mockReadFile).toHaveBeenCalledWith('/some/path/kibana.yml', 'utf-8');

expect(mockWriteFile).toHaveBeenCalledTimes(2);
expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/some/path/ca_1234.crt",
"ca-content",
],
Array [
"/some/path/kibana.yml",
"# Default Kibana configuration for docker target
server.host: \\"0.0.0.0\\"
server.shutdownTimeout: \\"5s\\"
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.serviceAccountToken: some-value
elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt]
",
],
]
`);
});
});

describe('with existing config (with conflicts)', () => {
beforeEach(() => {
jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('some date');
mockReadFile.mockResolvedValue(
'# Default Kibana configuration for docker target\nserver.host: "0.0.0.0"\nserver.shutdownTimeout: "5s"\nelasticsearch.hosts: [ "http://elasticsearch:9200" ]\n\nmonitoring.ui.container.elasticsearch.enabled: true'
);
});

it('can successfully write CA certificate and elasticsearch config', async () => {
await expect(
kibanaConfigWriter.writeConfig({
caCert: 'ca-content',
host: 'some-host',
serviceAccountToken: { name: 'some-token', value: 'some-value' },
})
).resolves.toBeUndefined();

expect(mockReadFile).toHaveBeenCalledTimes(1);
expect(mockReadFile).toHaveBeenCalledWith('/some/path/kibana.yml', 'utf-8');

expect(mockWriteFile).toHaveBeenCalledTimes(2);
expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/some/path/ca_1234.crt",
"ca-content",
],
Array [
"/some/path/kibana.yml",
"### >>>>>>> BACKUP START: Kibana interactive setup (some date)
# Default Kibana configuration for docker target
#server.host: \\"0.0.0.0\\"
#server.shutdownTimeout: \\"5s\\"
#elasticsearch.hosts: [ \\"http://elasticsearch:9200\\" ]
#monitoring.ui.container.elasticsearch.enabled: true
### >>>>>>> BACKUP END: Kibana interactive setup (some date)
# This section was automatically generated during setup.
server.host: 0.0.0.0
server.shutdownTimeout: 5s
elasticsearch.hosts: [some-host]
monitoring.ui.container.elasticsearch.enabled: true
elasticsearch.serviceAccountToken: some-value
elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt]
",
],
]
`);
});
});
});
});
Loading

0 comments on commit 1eed669

Please sign in to comment.