mirror of https://github.com/portainer/portainer
fix: 2.24 regressions (#190)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com> Co-authored-by: LP B <xAt0mZ@users.noreply.github.com> Co-authored-by: testA113 <aliharriss1995@gmail.com> Co-authored-by: oscarzhou <oscar.zhou@portainer.io>release/2.24.1 2.24.1
parent
5d311031e3
commit
b46bff06c6
|
@ -5,82 +5,110 @@
|
||||||
library css for buttons is overriden by `.widget .widget-body button`
|
library css for buttons is overriden by `.widget .widget-body button`
|
||||||
so we have to force margin: 0
|
so we have to force margin: 0
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-calendar button {
|
.react-daterange-picker__calendar .react-calendar button {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extending Calendar.css from react-datetime-picker
|
Extending Calendar.css from react-daterange-picker__calendar
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-calendar {
|
.react-daterange-picker__calendar .react-calendar {
|
||||||
background: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
color: var(--text-main-color);
|
color: var(--text-main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* calendar nav buttons */
|
/* calendar nav buttons */
|
||||||
.react-datetime-picker .react-calendar__navigation button:disabled {
|
.react-daterange-picker__calendar .react-calendar__navigation button:disabled {
|
||||||
background-color: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
@apply opacity-60;
|
@apply opacity-60;
|
||||||
@apply brightness-95 th-dark:brightness-110;
|
@apply brightness-95 th-dark:brightness-110;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__navigation button:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__navigation button:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:focus {
|
||||||
background-color: var(--bg-daterangepicker-color);
|
background: var(--bg-daterangepicker-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* date tile */
|
/* date tile */
|
||||||
.react-datetime-picker .react-calendar__tile:disabled {
|
.react-daterange-picker__calendar .react-calendar__tile:disabled {
|
||||||
background-color: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
@apply opacity-60;
|
@apply opacity-60;
|
||||||
@apply brightness-95 th-dark:brightness-110;
|
@apply brightness-95 th-dark:brightness-110;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile:enabled:focus {
|
||||||
background-color: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* today's date tile */
|
/* today's date tile */
|
||||||
.react-datetime-picker .react-calendar__tile--now {
|
.react-daterange-picker__calendar .react-calendar__tile--now {
|
||||||
/* use background color to avoid white on yellow in dark/high contrast modes */
|
|
||||||
@apply th-highcontrast:text-[color:var(--bg-calendar-color)] th-dark:text-[color:var(--bg-calendar-color)];
|
@apply th-highcontrast:text-[color:var(--bg-calendar-color)] th-dark:text-[color:var(--bg-calendar-color)];
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile--now:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--now:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* probably date tile in range */
|
/* probably date tile in range */
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive {
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive {
|
||||||
background: var(--bg-daterangepicker-end-date);
|
background: var(--bg-daterangepicker-end-date);
|
||||||
color: var(--text-daterangepicker-end-date);
|
color: var(--text-daterangepicker-end-date);
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* selected date tile */
|
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--active {
|
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-active);
|
|
||||||
color: var(--text-daterangepicker-active);
|
|
||||||
}
|
|
||||||
.react-datetime-picker .react-calendar__tile--active:enabled:hover,
|
|
||||||
.react-datetime-picker .react-calendar__tile--active:enabled:focus {
|
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar
|
||||||
|
.react-calendar__month-view__days__day:hover:not(.react-daterange-picker__calendar .react-calendar__tile--hoverEnd):not(
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverStart
|
||||||
|
):not(.react-calendar__tile--active) {
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* on range select hover */
|
/* on range select hover */
|
||||||
.react-datetime-picker .react-calendar--selectRange .react-calendar__tile--hover {
|
.react-daterange-picker__calendar .react-calendar--selectRange .react-calendar__tile--hover {
|
||||||
background-color: var(--bg-daterangepicker-in-range);
|
background: var(--bg-daterangepicker-in-range);
|
||||||
color: var(--text-daterangepicker-in-range);
|
color: var(--text-daterangepicker-in-range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extending DateTimePicker.css from react-datetime-picker
|
Extending DateTimePicker.css from react-daterange-picker__calendar
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-datetime-picker--disabled {
|
.react-daterange-picker__calendar .react-daterange-picker__calendar--disabled {
|
||||||
@apply opacity-40;
|
@apply opacity-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* selected date tile */
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--active {
|
||||||
|
background: var(--bg-daterangepicker-active) !important;
|
||||||
|
color: var(--text-daterangepicker-active) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--rangeStart:not(.react-calendar__tile--rangeEnd),
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverStart {
|
||||||
|
border-top-left-radius: 0.25rem;
|
||||||
|
border-bottom-left-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--rangeEnd:not(.react-calendar__tile--rangeStart),
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverEnd {
|
||||||
|
border-top-right-radius: 0.25rem;
|
||||||
|
border-bottom-right-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__month-view__days__day--weekend {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--active.react-calendar__month-view__days__day--weekend {
|
||||||
|
color: var(--text-daterangepicker-active);
|
||||||
|
}
|
||||||
|
|
|
@ -14,13 +14,8 @@ type StringPortBinding = {
|
||||||
containerPort: number;
|
containerPort: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NumericPortBinding = {
|
|
||||||
hostPort: number;
|
|
||||||
protocol: Protocol;
|
|
||||||
containerPort: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RangePortBinding = {
|
type RangePortBinding = {
|
||||||
|
hostIp: string;
|
||||||
hostPort: Range;
|
hostPort: Range;
|
||||||
protocol: Protocol;
|
protocol: Protocol;
|
||||||
containerPort: Range;
|
containerPort: Range;
|
||||||
|
@ -42,9 +37,7 @@ export function toViewModel(portBindings: PortMap): Values {
|
||||||
return value === 'tcp' || value === 'udp';
|
return value === 'tcp' || value === 'udp';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePorts(
|
function parsePorts(portBindings: PortMap): Array<StringPortBinding> {
|
||||||
portBindings: PortMap
|
|
||||||
): Array<StringPortBinding | NumericPortBinding> {
|
|
||||||
return Object.entries(portBindings).flatMap(([key, bindings]) => {
|
return Object.entries(portBindings).flatMap(([key, bindings]) => {
|
||||||
const [containerPort, protocol] = key.split('/');
|
const [containerPort, protocol] = key.split('/');
|
||||||
|
|
||||||
|
@ -63,15 +56,24 @@ export function toViewModel(portBindings: PortMap): Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindings.map((binding) => {
|
return bindings.map((binding) => {
|
||||||
|
let port = '';
|
||||||
|
if (binding.HostPort) {
|
||||||
|
port = binding.HostPort;
|
||||||
|
}
|
||||||
|
if (binding.HostIp) {
|
||||||
|
port = `${binding.HostIp}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (binding.HostPort?.includes('-')) {
|
if (binding.HostPort?.includes('-')) {
|
||||||
|
// Range port
|
||||||
return {
|
return {
|
||||||
hostPort: binding.HostPort,
|
hostPort: port,
|
||||||
protocol,
|
protocol,
|
||||||
containerPort: containerPortNumber,
|
containerPort: containerPortNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
hostPort: parseInt(binding.HostPort || '0', 10),
|
hostPort: port,
|
||||||
protocol,
|
protocol,
|
||||||
containerPort: containerPortNumber,
|
containerPort: containerPortNumber,
|
||||||
};
|
};
|
||||||
|
@ -79,9 +81,9 @@ export function toViewModel(portBindings: PortMap): Values {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortPorts(ports: Array<StringPortBinding | NumericPortBinding>) {
|
function sortPorts(ports: Array<StringPortBinding>) {
|
||||||
const rangePorts = ports.filter(isStringPortBinding);
|
const rangePorts = ports.filter(isRangePortBinding);
|
||||||
const nonRangePorts = ports.filter(isNumericPortBinding);
|
const nonRangePorts = ports.filter((port) => !isRangePortBinding(port));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rangePorts,
|
rangePorts,
|
||||||
|
@ -93,27 +95,40 @@ export function toViewModel(portBindings: PortMap): Values {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function combinePorts(ports: Array<NumericPortBinding>) {
|
function combinePorts(ports: Array<StringPortBinding>) {
|
||||||
return ports
|
return ports
|
||||||
.reduce((acc, port) => {
|
.reduce((acc, port) => {
|
||||||
|
let hostIp = '';
|
||||||
|
let hostPort = 0;
|
||||||
|
if (port.hostPort.includes(':')) {
|
||||||
|
const [ipStr, portStr] = port.hostPort.split(':');
|
||||||
|
hostIp = ipStr;
|
||||||
|
hostPort = parseInt(portStr || '0', 10);
|
||||||
|
} else {
|
||||||
|
hostPort = parseInt(port.hostPort || '0', 10);
|
||||||
|
}
|
||||||
|
|
||||||
const lastPort = acc[acc.length - 1];
|
const lastPort = acc[acc.length - 1];
|
||||||
if (
|
if (
|
||||||
lastPort &&
|
lastPort &&
|
||||||
|
lastPort.hostIp === hostIp &&
|
||||||
lastPort.containerPort.end === port.containerPort - 1 &&
|
lastPort.containerPort.end === port.containerPort - 1 &&
|
||||||
lastPort.hostPort.end === port.hostPort - 1 &&
|
lastPort.hostPort.end === hostPort - 1 &&
|
||||||
lastPort.protocol === port.protocol
|
lastPort.protocol === port.protocol
|
||||||
) {
|
) {
|
||||||
|
lastPort.hostIp = hostIp;
|
||||||
lastPort.containerPort.end = port.containerPort;
|
lastPort.containerPort.end = port.containerPort;
|
||||||
lastPort.hostPort.end = port.hostPort;
|
lastPort.hostPort.end = hostPort;
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...acc,
|
...acc,
|
||||||
{
|
{
|
||||||
|
hostIp,
|
||||||
hostPort: {
|
hostPort: {
|
||||||
start: port.hostPort,
|
start: hostPort,
|
||||||
end: port.hostPort,
|
end: hostPort,
|
||||||
},
|
},
|
||||||
containerPort: {
|
containerPort: {
|
||||||
start: port.containerPort,
|
start: port.containerPort,
|
||||||
|
@ -123,34 +138,32 @@ export function toViewModel(portBindings: PortMap): Values {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [] as Array<RangePortBinding>)
|
}, [] as Array<RangePortBinding>)
|
||||||
.map(({ protocol, containerPort, hostPort }) => ({
|
.map(({ protocol, containerPort, hostPort, hostIp }) => ({
|
||||||
hostPort: getRange(hostPort.start, hostPort.end),
|
hostPort: getRange(hostPort.start, hostPort.end, hostIp),
|
||||||
containerPort: getRange(containerPort.start, containerPort.end),
|
containerPort: getRange(containerPort.start, containerPort.end),
|
||||||
protocol,
|
protocol,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function getRange(start: number, end: number): string {
|
function getRange(start: number, end: number, hostIp?: string): string {
|
||||||
if (start === end) {
|
if (start === end) {
|
||||||
if (start === 0) {
|
if (start === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hostIp) {
|
||||||
|
return `${hostIp}:${start}`;
|
||||||
|
}
|
||||||
return start.toString();
|
return start.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hostIp) {
|
||||||
|
return `${hostIp}:${start}-${end}`;
|
||||||
|
}
|
||||||
return `${start}-${end}`;
|
return `${start}-${end}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNumericPortBinding(
|
function isRangePortBinding(port: StringPortBinding): boolean {
|
||||||
port: StringPortBinding | NumericPortBinding
|
return port.hostPort.includes('-');
|
||||||
): port is NumericPortBinding {
|
|
||||||
return port.hostPort !== 'string';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStringPortBinding(
|
|
||||||
port: StringPortBinding | NumericPortBinding
|
|
||||||
): port is StringPortBinding {
|
|
||||||
return port.hostPort === 'string';
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,15 @@ export async function buildImageFromDockerfileContentAndFiles(
|
||||||
const dockerfile = new Blob([content], { type: 'text/plain' });
|
const dockerfile = new Blob([content], { type: 'text/plain' });
|
||||||
const uploadFiles = [dockerfile, ...files];
|
const uploadFiles = [dockerfile, ...files];
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
uploadFiles.forEach((file, index) => {
|
||||||
|
formData.append(`file${index}`, file);
|
||||||
|
});
|
||||||
|
|
||||||
return buildImage(
|
return buildImage(
|
||||||
environmentId,
|
environmentId,
|
||||||
{ t: names },
|
{ t: names },
|
||||||
{ file: uploadFiles },
|
formData,
|
||||||
'multipart/form-data'
|
'multipart/form-data'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
export interface ActivityLog {
|
interface BaseActivityLog {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
action: string;
|
action: string;
|
||||||
context: string;
|
context: string;
|
||||||
id: number;
|
id: number;
|
||||||
payload: object;
|
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
export interface ActivityLogResponse extends BaseActivityLog {
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityLog extends BaseActivityLog {
|
||||||
|
payload: string | object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityLogsResponse {
|
||||||
|
logs: Array<ActivityLogResponse>;
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
|
|
||||||
import { isBE } from '../../feature-flags/feature-flags.service';
|
import { isBE } from '../../feature-flags/feature-flags.service';
|
||||||
|
|
||||||
import { ActivityLog } from './types';
|
import { ActivityLogResponse, ActivityLogsResponse } from './types';
|
||||||
|
|
||||||
export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
|
export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
|
||||||
export type SortKey = (typeof sortKeys)[number];
|
export type SortKey = (typeof sortKeys)[number];
|
||||||
|
@ -30,19 +30,18 @@ export function useActivityLogs(query: Query) {
|
||||||
queryKey: ['activityLogs', query] as const,
|
queryKey: ['activityLogs', query] as const,
|
||||||
queryFn: () => fetchActivityLogs(query),
|
queryFn: () => fetchActivityLogs(query),
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
|
select: (data) => ({
|
||||||
|
...data,
|
||||||
|
logs: decorateLogs(data.logs),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityLogsResponse {
|
|
||||||
logs: Array<ActivityLog>;
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
||||||
try {
|
try {
|
||||||
if (!isBE) {
|
if (!isBE) {
|
||||||
return {
|
return {
|
||||||
logs: [{}, {}, {}, {}, {}] as Array<ActivityLog>,
|
logs: [{}, {}, {}, {}, {}] as Array<ActivityLogResponse>,
|
||||||
totalCount: 5,
|
totalCount: 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -56,3 +55,40 @@ async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
||||||
throw parseAxiosError(err, 'Failed loading user activity logs csv');
|
throw parseAxiosError(err, 'Failed loading user activity logs csv');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates logs with the payload parsed from base64
|
||||||
|
*/
|
||||||
|
function decorateLogs(logs?: ActivityLogResponse[]) {
|
||||||
|
if (!logs || logs.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs.map((log) => ({
|
||||||
|
...log,
|
||||||
|
payload: parseBase64AsObject(log.payload),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBase64AsObject(value: string): string | object {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(safeAtob(value));
|
||||||
|
} catch (err) {
|
||||||
|
return safeAtob(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeAtob(value: string) {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return window.atob(value);
|
||||||
|
} catch (err) {
|
||||||
|
// If the payload is not base64 encoded, return the original value
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,32 +70,24 @@ func withComposeService(
|
||||||
return withCli(ctx, options, func(ctx context.Context, cli *command.DockerCli) error {
|
return withCli(ctx, options, func(ctx context.Context, cli *command.DockerCli) error {
|
||||||
composeService := compose.NewComposeService(cli)
|
composeService := compose.NewComposeService(cli)
|
||||||
|
|
||||||
|
if len(filePaths) == 0 {
|
||||||
|
return composeFn(composeService, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := parseEnvironment(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
configDetails := types.ConfigDetails{
|
configDetails := types.ConfigDetails{
|
||||||
WorkingDir: options.WorkingDir,
|
Environment: env,
|
||||||
Environment: make(map[string]string),
|
WorkingDir: filepath.Dir(filePaths[0]),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range filePaths {
|
for _, p := range filePaths {
|
||||||
configDetails.ConfigFiles = append(configDetails.ConfigFiles, types.ConfigFile{Filename: p})
|
configDetails.ConfigFiles = append(configDetails.ConfigFiles, types.ConfigFile{Filename: p})
|
||||||
}
|
}
|
||||||
|
|
||||||
envFile := make(map[string]string)
|
|
||||||
|
|
||||||
if options.EnvFilePath != "" {
|
|
||||||
env, err := dotenv.GetEnvFromFile(make(map[string]string), []string{options.EnvFilePath})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get the environment from the env file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
maps.Copy(envFile, env)
|
|
||||||
|
|
||||||
configDetails.Environment = env
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configDetails.ConfigFiles) == 0 {
|
|
||||||
return composeFn(composeService, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
project, err := loader.LoadWithContext(ctx, configDetails,
|
project, err := loader.LoadWithContext(ctx, configDetails,
|
||||||
func(o *loader.Options) {
|
func(o *loader.Options) {
|
||||||
o.SkipResolveEnvironment = true
|
o.SkipResolveEnvironment = true
|
||||||
|
@ -110,21 +102,20 @@ func withComposeService(
|
||||||
return fmt.Errorf("failed to load the compose file: %w", err)
|
return fmt.Errorf("failed to load the compose file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.EnvFilePath != "" {
|
// Work around compose path handling
|
||||||
// Work around compose path handling
|
for i, service := range project.Services {
|
||||||
for i, service := range project.Services {
|
for j, envFile := range service.EnvFiles {
|
||||||
for j, envFile := range service.EnvFiles {
|
if !filepath.IsAbs(envFile.Path) {
|
||||||
if !filepath.IsAbs(envFile.Path) {
|
project.Services[i].EnvFiles[j].Path = filepath.Join(configDetails.WorkingDir, envFile.Path)
|
||||||
project.Services[i].EnvFiles[j].Path = filepath.Join(project.WorkingDir, envFile.Path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if p, err := project.WithServicesEnvironmentResolved(true); err == nil {
|
// Set the services environment variables
|
||||||
project = p
|
if p, err := project.WithServicesEnvironmentResolved(true); err == nil {
|
||||||
} else {
|
project = p
|
||||||
return fmt.Errorf("failed to resolve services environment: %w", err)
|
} else {
|
||||||
}
|
return fmt.Errorf("failed to resolve services environment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return composeFn(composeService, project)
|
return composeFn(composeService, project)
|
||||||
|
@ -136,6 +127,8 @@ func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, option
|
||||||
return withComposeService(ctx, filePaths, options.Options, func(composeService api.Service, project *types.Project) error {
|
return withComposeService(ctx, filePaths, options.Options, func(composeService api.Service, project *types.Project) error {
|
||||||
addServiceLabels(project, false)
|
addServiceLabels(project, false)
|
||||||
|
|
||||||
|
project = project.WithoutUnnecessaryResources()
|
||||||
|
|
||||||
var opts api.UpOptions
|
var opts api.UpOptions
|
||||||
if options.ForceRecreate {
|
if options.ForceRecreate {
|
||||||
opts.Create.Recreate = api.RecreateForce
|
opts.Create.Recreate = api.RecreateForce
|
||||||
|
@ -144,6 +137,10 @@ func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, option
|
||||||
opts.Create.RemoveOrphans = options.RemoveOrphans
|
opts.Create.RemoveOrphans = options.RemoveOrphans
|
||||||
opts.Start.CascadeStop = options.AbortOnContainerExit
|
opts.Start.CascadeStop = options.AbortOnContainerExit
|
||||||
|
|
||||||
|
if err := composeService.Build(ctx, project, api.BuildOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("compose build operation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := composeService.Up(ctx, project, opts); err != nil {
|
if err := composeService.Up(ctx, project, opts); err != nil {
|
||||||
return fmt.Errorf("compose up operation failed: %w", err)
|
return fmt.Errorf("compose up operation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -256,10 +253,36 @@ func addServiceLabels(project *types.Project, oneOff bool) {
|
||||||
api.ProjectLabel: project.Name,
|
api.ProjectLabel: project.Name,
|
||||||
api.ServiceLabel: s.Name,
|
api.ServiceLabel: s.Name,
|
||||||
api.VersionLabel: api.ComposeVersion,
|
api.VersionLabel: api.ComposeVersion,
|
||||||
api.WorkingDirLabel: "/",
|
api.WorkingDirLabel: project.WorkingDir,
|
||||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||||
api.OneoffLabel: oneOffLabel,
|
api.OneoffLabel: oneOffLabel,
|
||||||
}
|
}
|
||||||
project.Services[i] = s
|
project.Services[i] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseEnvironment(options libstack.Options) (map[string]string, error) {
|
||||||
|
env := make(map[string]string)
|
||||||
|
|
||||||
|
for _, envLine := range options.Env {
|
||||||
|
e, err := dotenv.UnmarshalWithLookup(envLine, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse environment variables: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
maps.Copy(env, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.EnvFilePath == "" {
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := dotenv.GetEnvFromFile(make(map[string]string), []string{options.EnvFilePath})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get the environment from the env file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
maps.Copy(env, e)
|
||||||
|
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
bail: 2,
|
bail: 2,
|
||||||
include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
|
include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
|
||||||
|
env: {
|
||||||
|
PORTAINER_EDITION: 'CE',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
|
plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue