diff --git a/src/device-api/actions.ts b/src/device-api/actions.ts index 036eda40..8c5764f9 100644 --- a/src/device-api/actions.ts +++ b/src/device-api/actions.ts @@ -10,7 +10,7 @@ import * as hostConfig from '../host-config'; import type { HostConfiguration, LegacyHostConfiguration, -} from '../host-config'; +} from '../host-config/types'; import * as applicationManager from '../compose/application-manager'; import type { CompositionStepAction } from '../compose/composition-steps'; import { generateStep } from '../compose/composition-steps'; diff --git a/src/host-config/index.ts b/src/host-config/index.ts index 19259f55..1c440c4e 100644 --- a/src/host-config/index.ts +++ b/src/host-config/index.ts @@ -12,17 +12,14 @@ import { pathOnRoot } from '../lib/host-utils'; import log from '../lib/supervisor-console'; import * as updateLock from '../lib/update-lock'; -export * from './proxy'; -export * from './types'; - const hostnamePath = pathOnRoot('/etc/hostname'); -export async function readHostname() { +async function readHostname() { const hostnameData = await fs.readFile(hostnamePath, 'utf-8'); return hostnameData.trim(); } -export async function setHostname(val: string) { +async function setHostname(val: string) { let hostname = val; // If hostname is an empty string, return first 7 digits of device uuid if (!val) { @@ -59,7 +56,7 @@ export function parse( throw new Error('Could not parse host config input to a valid format'); } -export function patchProxy( +function patchProxy( currentConf: RedsocksConfig, inputConf: Partial<{ redsocks: Partial; @@ -123,10 +120,12 @@ export async function patch( } export async function get(): Promise { + const proxy = await readProxy(); return { network: { hostname: await readHostname(), - proxy: await readProxy(), + // Only return proxy if readProxy is not undefined + ...(proxy && { proxy }), }, }; } diff --git a/src/host-config/proxy.ts b/src/host-config/proxy.ts index 7b846485..6efd8fec 100644 --- a/src/host-config/proxy.ts +++ b/src/host-config/proxy.ts @@ -219,7 +219,7 @@ async function restartProxyServices() { } } -export async function readNoProxy(): Promise { +async function readNoProxy(): Promise { try { const noProxy = await readFromBoot(noProxyPath, 'utf-8') // Prevent empty newline from being reported as a noProxy address @@ -238,7 +238,7 @@ export async function readNoProxy(): Promise { } } -export async function setNoProxy(list: Nullable) { +async function setNoProxy(list: Nullable) { const current = await readNoProxy(); if (!list || !Array.isArray(list) || !list.length) { await unlinkAll(noProxyPath); diff --git a/test/integration/host-config.spec.ts b/test/integration/host-config.spec.ts index a3ca19a1..cafa4b7b 100644 --- a/test/integration/host-config.spec.ts +++ b/test/integration/host-config.spec.ts @@ -7,7 +7,6 @@ import { stub } from 'sinon'; import * as fs from 'fs/promises'; import { get, patch } from '~/src/host-config'; -import * as hostConfig from '~/src/host-config'; import * as config from '~/src/config'; import * as applicationManager from '~/src/compose/application-manager'; import type { InstancedAppState } from '~/src/compose/types'; @@ -83,96 +82,6 @@ describe('host-config', () => { (applicationManager.getCurrentApps as SinonStub).restore(); }); - describe('hostname', () => { - it('reads hostname', async () => { - expect(await hostConfig.readHostname()).to.equal('deadbeef'); - }); - - it('sets hostname', async () => { - await hostConfig.setHostname('test'); - expect(await config.get('hostname')).to.equal('test'); - }); - - it('sets hostname to first 7 characters of UUID if empty', async () => { - await config.set({ uuid: '1234567' }); - await hostConfig.setHostname(''); - expect(await config.get('hostname')).to.equal('1234567'); - }); - }); - - describe('noProxy', () => { - it('reads IPs to exclude from proxy', async () => { - expect(await hostConfig.readNoProxy()).to.deep.equal([ - '152.10.30.4', - '253.1.1.0/16', - ]); - }); - - it('sets IPs to exclude from proxy', async () => { - await hostConfig.setNoProxy(['balena.io', '1.1.1.1']); - expect(await fs.readFile(noProxy, 'utf-8')).to.equal( - 'balena.io\n1.1.1.1', - ); - await hostConfig.setNoProxy(['balena.io', '2.2.2.2']); - expect(await fs.readFile(noProxy, 'utf-8')).to.equal( - 'balena.io\n2.2.2.2', - ); - }); - - it('returns whether noProxy was changed', async () => { - // Set initial no_proxy as empty - await hostConfig.setNoProxy([]); - - // Change no_proxy - expect(await hostConfig.setNoProxy(['balena.io', '1.1.1.1'])).to.be.true; - expect(await hostConfig.readNoProxy()) - .to.deep.include.members(['balena.io', '1.1.1.1']) - .and.have.lengthOf(2); - - // Change no_proxy to same value - expect(await hostConfig.setNoProxy(['1.1.1.1', 'balena.io'])).to.be.false; - expect(await hostConfig.readNoProxy()) - .to.deep.include.members(['balena.io', '1.1.1.1']) - .and.have.lengthOf(2); - - // Remove a value - expect(await hostConfig.setNoProxy(['1.1.1.1'])).to.be.true; - expect(await hostConfig.readNoProxy()) - .to.deep.include.members(['1.1.1.1']) - .and.have.lengthOf(1); - - // Add a value - expect(await hostConfig.setNoProxy(['2.2.2.2', '1.1.1.1'])).to.be.true; - expect(await hostConfig.readNoProxy()) - .to.deep.include.members(['2.2.2.2', '1.1.1.1']) - .and.have.lengthOf(2); - - // Remove no_proxy - expect(await hostConfig.setNoProxy([])).to.be.true; - expect(await hostConfig.readNoProxy()).to.deep.equal([]); - }); - - it('removes no_proxy file if empty or invalid', async () => { - // Set initial no_proxy - await hostConfig.setNoProxy(['2.2.2.2']); - expect(await hostConfig.readNoProxy()).to.deep.equal(['2.2.2.2']); - - // Set to empty array - await hostConfig.setNoProxy([]); - expect(await hostConfig.readNoProxy()).to.deep.equal([]); - expect(await fs.readdir(proxyBase)).to.not.have.members(['no_proxy']); - - // Reset initial no_proxy - await hostConfig.setNoProxy(['2.2.2.2']); - expect(await hostConfig.readNoProxy()).to.deep.equal(['2.2.2.2']); - - // Set to invalid value - await hostConfig.setNoProxy(null as any); - expect(await hostConfig.readNoProxy()).to.deep.equal([]); - expect(await fs.readdir(proxyBase)).to.not.have.members(['no_proxy']); - }); - }); - it('reads proxy configs and hostname', async () => { const { network } = await get(); expect(network).to.have.property('hostname', 'deadbeef'); @@ -204,10 +113,10 @@ describe('host-config', () => { try { await patch({ network: { proxy: {} } }, true); - expect(await hostConfig.readProxy()).to.be.undefined; } catch (e: unknown) { expect.fail(`Expected hostConfig.patch to not throw, but got ${e}`); } + expect(await get()).to.deep.equal({ network: { hostname: 'deadbeef' } }); }); it('patches hostname regardless of update locks', async () => { @@ -279,7 +188,7 @@ describe('host-config', () => { it('patches proxy to empty if input is empty', async () => { await patch({ network: { proxy: {} } }); const { network } = await get(); - expect(network).to.have.property('proxy', undefined); + expect(network).to.not.have.property('proxy'); }); it('keeps current proxy if input is invalid', async () => { @@ -341,7 +250,7 @@ describe('host-config', () => { it('patches redsocks.conf to be empty if prompted', async () => { await patch({ network: { proxy: {} } }); const { network } = await get(); - expect(network).to.have.property('proxy', undefined); + expect(network).to.not.have.property('proxy'); expect(await fs.readdir(proxyBase)).to.not.have.members([ 'redsocks.conf', 'no_proxy', diff --git a/test/unit/host-config.spec.ts b/test/unit/host-config.spec.ts index 8d4f76de..9fbd76f9 100644 --- a/test/unit/host-config.spec.ts +++ b/test/unit/host-config.spec.ts @@ -3,8 +3,8 @@ import { stripIndent } from 'common-tags'; import type { SinonStub } from 'sinon'; import * as hostConfig from '~/src/host-config'; -import { RedsocksConf } from '~/src/host-config'; -import type { RedsocksConfig, ProxyConfig } from '~/src/host-config'; +import { RedsocksConf } from '~/src/host-config/proxy'; +import type { RedsocksConfig, ProxyConfig } from '~/src/host-config/types'; import log from '~/lib/supervisor-console'; describe('RedsocksConf', () => { @@ -379,57 +379,6 @@ describe('RedsocksConf', () => { }); describe('src/host-config', () => { - describe('patchProxy', () => { - it('patches RedsocksConfig with new values', () => { - const current = { - redsocks: { - type: 'socks5', - ip: 'example.org', - port: 1080, - login: '"foo"', - password: '"bar"', - }, - } as RedsocksConfig; - const input = { - redsocks: { - type: 'http-connect', - ip: 'test.balena.io', - }, - } as any; - const patched = hostConfig.patchProxy(current, input); - expect(patched).to.deep.equal({ - redsocks: { - // Patched fields are updated - type: 'http-connect', - ip: 'test.balena.io', - // Unpatched fields retain their original values - port: 1080, - login: '"foo"', - password: '"bar"', - }, - }); - }); - - it('returns empty RedsocksConfig if redsocks config block is empty or invalid', () => { - const current: RedsocksConfig = { - redsocks: { - type: 'socks5', - ip: 'example.org', - port: 1080, - login: '"foo"', - password: '"bar"', - }, - }; - expect(hostConfig.patchProxy(current, { redsocks: {} })).to.deep.equal( - {}, - ); - expect( - hostConfig.patchProxy(current, { redsocks: true } as any), - ).to.deep.equal({}); - expect(hostConfig.patchProxy(current, {})).to.deep.equal({}); - }); - }); - describe('parse', () => { it('parses valid HostConfiguration', () => { const conf = {