diff --git a/src/Components/CloudProviderConfig/CloudProviderConfig.tsx b/src/Components/CloudProviderConfig/CloudProviderConfig.tsx index 6751f4c8..183685c8 100644 --- a/src/Components/CloudProviderConfig/CloudProviderConfig.tsx +++ b/src/Components/CloudProviderConfig/CloudProviderConfig.tsx @@ -1,17 +1,62 @@ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; import { PageSection, Wizard, WizardStep } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import { AWSConfig } from './AWSConfig'; +import { isAwsStepValid } from './validators'; +import { + changeAWSBucketName, + changeAWSCredsPath, + reinitializeAWSConfig, +} from '../../store/cloudProviderConfigSlice'; +import { useGetWorkerConfigQuery } from '../../store/cockpit/cockpitApi'; +import { AWSWorkerConfig } from '../../store/cockpit/types'; +import { useAppDispatch } from '../../store/hooks'; import { resolveRelPath } from '../../Utilities/path'; import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader'; export const CloudProviderConfig = () => { const navigate = useNavigate(); + const dispatch = useAppDispatch(); const handleClose = () => navigate(resolveRelPath('')); + const { data, error } = useGetWorkerConfigQuery({}); + + const initAWSConfig = useCallback( + (config: AWSWorkerConfig | undefined) => { + if (!config) { + dispatch(reinitializeAWSConfig()); + return; + } + + const { bucket, credentials } = config; + if (bucket && bucket !== '') { + dispatch(changeAWSBucketName(bucket)); + } + + if (credentials && credentials !== '') { + dispatch(changeAWSCredsPath(credentials)); + } + }, + [dispatch] + ); + + useEffect(() => { + initAWSConfig(data?.aws); + }, [data, initAWSConfig]); + + if (error) { + // TODO: improve error alert + return ( +
+ There was an error reading the `/etc/osbuild-worker/osbuild-worker.toml` + config file +
+ ); + } + return ( <> @@ -22,6 +67,7 @@ export const CloudProviderConfig = () => { id="aws-config" footer={{ nextButtonText: 'Submit', + isNextDisabled: !isAwsStepValid(config), isBackDisabled: true, }} > diff --git a/src/Components/CloudProviderConfig/validators/index.tsx b/src/Components/CloudProviderConfig/validators/index.tsx index cb5048ea..1503adfe 100644 --- a/src/Components/CloudProviderConfig/validators/index.tsx +++ b/src/Components/CloudProviderConfig/validators/index.tsx @@ -1,11 +1,37 @@ import path from 'path'; -export const isAwsBucketValid = (bucket: string): boolean => { +import { AWSWorkerConfig } from '../../../store/cockpit/types'; + +export const isAwsBucketValid = (bucket?: string): boolean => { + if (!bucket || bucket === '') { + return false; + } + const regex = /^[a-z0-9](?:[a-z0-9]|[-.](?=[a-z0-9])){1,61}[a-z0-9]$/; return regex.test(bucket); }; -export const isAwsCredsPathValid = (credsPath: string): boolean => { +export const isAwsCredsPathValid = (credsPath?: string): boolean => { + if (!credsPath || credsPath === '') { + return false; + } + const validPathPattern = /^(\/[^/\0]*)+\/?$/; return path.isAbsolute(credsPath) && validPathPattern.test(credsPath); }; + +export const isAwsStepValid = ( + config: AWSWorkerConfig | undefined +): boolean => { + if (!config) { + return true; + } + + if (!config.bucket && !config.credentials) { + return false; + } + + return ( + isAwsBucketValid(config.bucket) && isAwsCredsPathValid(config.credentials) + ); +}; diff --git a/src/store/cloudProviderConfigSlice.ts b/src/store/cloudProviderConfigSlice.ts index 55ab5d66..6dc03adc 100644 --- a/src/store/cloudProviderConfigSlice.ts +++ b/src/store/cloudProviderConfigSlice.ts @@ -8,6 +8,16 @@ export const initialState: CloudProviderConfigState = { aws: {}, }; +export const selectAWSConfig = (state: RootState) => { + if (Object.keys(state.cloudConfig.aws).length === 0) { + // just return undefined since the config is empty + // and we don't want to save `[aws]` header to the + // worker config file with no body + return undefined; + } + return state.cloudConfig.aws; +}; + export const selectAWSBucketName = (state: RootState) => { return state.cloudConfig.aws.bucket; }; @@ -20,6 +30,9 @@ export const cloudProviderConfigSlice = createSlice({ name: 'cloudConfig', initialState, reducers: { + reinitializeAWSConfig: (state) => { + state.aws = {}; + }, changeAWSBucketName: (state, action: PayloadAction) => { state.aws.bucket = action.payload; }, @@ -29,5 +42,8 @@ export const cloudProviderConfigSlice = createSlice({ }, }); -export const { changeAWSBucketName, changeAWSCredsPath } = - cloudProviderConfigSlice.actions; +export const { + reinitializeAWSConfig, + changeAWSBucketName, + changeAWSCredsPath, +} = cloudProviderConfigSlice.actions; diff --git a/src/store/cockpit/cockpitApi.ts b/src/store/cockpit/cockpitApi.ts index a0c1067d..2220cd59 100644 --- a/src/store/cockpit/cockpitApi.ts +++ b/src/store/cockpit/cockpitApi.ts @@ -18,6 +18,7 @@ import { v4 as uuidv4 } from 'uuid'; // the same unix socket. This allows us to split out the code a little // bit so that the `cockpitApi` doesn't become a monolith. import { contentSourcesApi } from './contentSourcesApi'; +import type { WorkerConfigResponse } from './types'; import { mapHostedToOnPrem, @@ -584,6 +585,24 @@ export const cockpitApi = contentSourcesApi.injectEndpoints({ } }, }), + getWorkerConfig: builder.query({ + queryFn: async () => { + try { + const config = await cockpit + .file('/etc/osbuild-worker/osbuild-worker.toml') + .read(); + + return { data: TOML.parse(config) }; + } catch (error) { + // no worker file error message + if (error.message === 'input is null') { + return { data: {} }; + } + + return { error }; + } + }, + }), }; }, // since we are inheriting some endpoints, @@ -607,4 +626,5 @@ export const { useGetComposesQuery, useGetBlueprintComposesQuery, useGetComposeStatusQuery, + useGetWorkerConfigQuery, } = cockpitApi;