-
-
Notifications
You must be signed in to change notification settings - Fork 296
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
feat: Use Environment variables for secrets capabilities #1702 #1877
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Saikrishna321 <[email protected]>
Co-authored-by: Saikrishna321 <[email protected]>
I am not sure if it makes sense to make all env variables secret. I assume there might be some which values we still would like to inspect in plain text |
Currently both environment variables and secrets are same and in frontend we just mask it whereas it remains plaintext in appium logs unless user explicitly filters it in logs. |
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
Also I don't really see the need of renaming |
Co-authored-by: Saikrishna321 <[email protected]>
Co-authored-by: Saikrishna321 <[email protected]>
@eglitise please help review |
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
app/common/renderer/components/Inspector/EnvironmentVariables.jsx
Outdated
Show resolved
Hide resolved
// Reload environment variables from persistent settings after session ends | ||
const loadEnvAction = loadEnvironmentVariables(); | ||
await loadEnvAction(dispatch); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this code is necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, I don't think this code is necessary because we only care about loading the env var values in the context of an active session. Quitting a session won't delete them from the persistent settings in any case.
Also, maybe it's better to use a |
Co-authored-by: Saikrishna321 <[email protected]>
Co-authored-by: Saikrishna321 <[email protected]>
ce4433b
to
2343535
Compare
Co-authored-by: Saikrishna321 <[email protected]>
2343535
to
3497162
Compare
@eglitise Can you please help review this as we have other PR's lined up? |
@SrinivasanTarget I will check this once I have some time, likely this weekend. I don't see how this should block you from creating new PRs though. |
One thing I can point out right now though - the approach of storing these vars in persistent settings. I thought about it some more and came to the conclusion that my suggestion about this was wrong. I'm not sure why these vars should be retained after quitting the session, and we can just reapply them when starting the next session. That lead me to think that the best way to store them would be in the saved session object, alongside the server and capability details. |
"invalidSessionFile": "Invalid session file", | ||
"Environment Variables": "Environment Variables", | ||
"Variable Name": "Variable Name", | ||
"Delete Variable": "Delete Variable" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This specific key is not used anymore
No worries take your time. I wanted to avoid conflicts as i will touching the same files. |
@eglitise I would prefer to keep it persistent so that between multiple session creations users doesnt have to re-enter env variables again and save it. Also what if server (inspector as plugin) is hosted at a central place in an org and environment variables (aka secrets). do correct me if im wrong. |
@SrinivasanTarget the user would not need to re-enter the env vars because they would be stored in the saved session object. They would only need to select the entry in the Saved Capability Sets tab (which should be renamed, but not in this PR), and the Inspector would apply the stored env vars the same way as it currently applies server and capability details. This approach would also allow specifying different sets of env vars for different session objects. |
t={t} | ||
envVars={environmentVariables} | ||
addVariable={addEnvironmentVariable} | ||
deleteVariable={deleteEnvironmentVariable} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just pass {...props}
for consistency with the other tabs. Note that the imported method names in EnvironmentVariables
will need to be changed, but this is better for tracking them anyway
case SET_ENVIRONMENT_VARIABLES: | ||
return { | ||
...state, | ||
environmentVariables: action.envVars, | ||
}; | ||
|
||
case ADD_ENVIRONMENT_VARIABLE: | ||
return { | ||
...state, | ||
environmentVariables: [ | ||
...(state.environmentVariables || []), | ||
{key: action.key, value: action.value}, | ||
], | ||
}; | ||
|
||
case DELETE_ENVIRONMENT_VARIABLE: | ||
return { | ||
...state, | ||
environmentVariables: (state.environmentVariables || []).filter( | ||
(envVar) => envVar.key !== action.key, | ||
), | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code can be removed since the Session reducer handles this functionality. The only use of this code is in loadEnvironmentVariables
in actions/Inspector.js
, but as I commented under that code, that method can also be removed.
@@ -75,12 +78,34 @@ const INITIAL_STATE = { | |||
isValidatingCapsJson: false, | |||
isAddingCloudProvider: false, | |||
addVendorPrefixes: true, | |||
environmentVariables: [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value here can be changed to new Map()
, so you can avoid unnecessary type conversion in the set/add/delete actions. Note that the add/remove methods in actions/Session.js
would also need to be adjusted to handle this.
@@ -1190,3 +1215,35 @@ export function initFromQueryString(loadNewSession) { | |||
} | |||
}; | |||
} | |||
|
|||
export function setEnvironmentVariables(envVars) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method isn't used anywhere
}; | ||
} | ||
|
||
export function loadEnvironmentVariables() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be renamed to setEnvironmentVariables
for consistency with setVisibleProviders
. I was initially confused by its call in components/Session/Session.jsx
.
|
||
export function addEnvironmentVariable(key, value) { | ||
return async (dispatch, getState) => { | ||
const currentEnvVars = getState().inspector.environmentVariables || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why these are retrieved from the inspector reducer (they aren't relevant anymore at that point). The variable also has a default value, so the additional empty array is not needed.
const currentEnvVars = getState().inspector.environmentVariables || []; | |
const currentEnvVars = getState().session.environmentVariables; |
|
||
export function deleteEnvironmentVariable(key) { | ||
return async (dispatch, getState) => { | ||
const currentEnvVars = getState().inspector.environmentVariables || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
const currentEnvVars = getState().inspector.environmentVariables || []; | |
const currentEnvVars = getState().session.environmentVariables; |
@@ -229,7 +236,25 @@ export function newSession(caps, attachSessId = null) { | |||
|
|||
dispatch({type: NEW_SESSION_REQUESTED, caps}); | |||
|
|||
// Get environment variables from state | |||
const environmentVariables = session.environmentVariables || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling for default value is not needed since it is guaranteed to be set
import {Button, Card, Input, Popconfirm, Space, Table, Tooltip} from 'antd'; | ||
import {useState} from 'react'; | ||
|
||
import SessionStyles from './Session.module.css'; | ||
|
||
const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { | ||
const [newVar, setNewVar] = useState({key: '', value: ''}); | ||
|
||
const tableData = Array.from(envVars, ([key, value]) => ({key, value})); | ||
|
||
const columns = [ | ||
{ | ||
title: t('Variable Name'), | ||
dataIndex: 'key', | ||
key: 'key', | ||
width: '40%', | ||
}, | ||
{ | ||
title: t('Value'), | ||
dataIndex: 'value', | ||
key: 'value', | ||
width: '40%', | ||
render: (text) => <Input.Password value={text} readOnly />, | ||
}, | ||
{ | ||
title: t('Actions'), | ||
key: 'action', | ||
width: '20%', | ||
render: (_, record) => ( | ||
<Tooltip zIndex={3} title={t('Delete')}> | ||
<Popconfirm | ||
zIndex={4} | ||
title={t('confirmDeletion')} | ||
placement="topRight" | ||
okText={t('OK')} | ||
cancelText={t('Cancel')} | ||
onConfirm={() => deleteVariable(record.key)} | ||
> | ||
<Button icon={<DeleteOutlined />} /> | ||
</Popconfirm> | ||
</Tooltip> | ||
), | ||
}, | ||
]; | ||
|
||
const handleAddVariable = () => { | ||
if (!newVar.key || !newVar.value) { | ||
return; | ||
} | ||
|
||
// Check for duplicate keys is no longer needed since Map handles this automatically | ||
addVariable(newVar.key, newVar.value); | ||
setNewVar({key: '', value: ''}); | ||
}; | ||
|
||
return ( | ||
<Card | ||
title={ | ||
<span> | ||
<SettingOutlined /> {t('Environment Variables')} | ||
</span> | ||
} | ||
className={SessionStyles['interaction-tab-card']} | ||
> | ||
<div className={SessionStyles.container}> | ||
<div className={SessionStyles.addForm}> | ||
<Space.Compact style={{width: '100%'}}> | ||
<Input | ||
placeholder={t('Variable Name')} | ||
value={newVar.key} | ||
onChange={(e) => setNewVar({...newVar, key: e.target.value})} | ||
/> | ||
<Input.Password | ||
placeholder={t('Value')} | ||
value={newVar.value} | ||
onChange={(e) => setNewVar({...newVar, value: e.target.value})} | ||
/> | ||
<Button | ||
type="primary" | ||
icon={<PlusOutlined />} | ||
onClick={handleAddVariable} | ||
disabled={!newVar.key || !newVar.value} | ||
> | ||
{t('Add')} | ||
</Button> | ||
</Space.Compact> | ||
</div> | ||
<Table | ||
columns={columns} | ||
dataSource={tableData} | ||
pagination={false} | ||
rowKey="key" | ||
size="small" | ||
/> | ||
</div> | ||
</Card> | ||
); | ||
}; | ||
|
||
export default EnvironmentVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really a fan of the Card
view, how about this?
I've also adjusted the table for better handling of more rows (added starting point for scroll + sticky header)
import {DeleteOutlined, PlusOutlined, SettingOutlined} from '@ant-design/icons'; | |
import {Button, Card, Input, Popconfirm, Space, Table, Tooltip} from 'antd'; | |
import {useState} from 'react'; | |
import SessionStyles from './Session.module.css'; | |
const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { | |
const [newVar, setNewVar] = useState({key: '', value: ''}); | |
const tableData = Array.from(envVars, ([key, value]) => ({key, value})); | |
const columns = [ | |
{ | |
title: t('Variable Name'), | |
dataIndex: 'key', | |
key: 'key', | |
width: '40%', | |
}, | |
{ | |
title: t('Value'), | |
dataIndex: 'value', | |
key: 'value', | |
width: '40%', | |
render: (text) => <Input.Password value={text} readOnly />, | |
}, | |
{ | |
title: t('Actions'), | |
key: 'action', | |
width: '20%', | |
render: (_, record) => ( | |
<Tooltip zIndex={3} title={t('Delete')}> | |
<Popconfirm | |
zIndex={4} | |
title={t('confirmDeletion')} | |
placement="topRight" | |
okText={t('OK')} | |
cancelText={t('Cancel')} | |
onConfirm={() => deleteVariable(record.key)} | |
> | |
<Button icon={<DeleteOutlined />} /> | |
</Popconfirm> | |
</Tooltip> | |
), | |
}, | |
]; | |
const handleAddVariable = () => { | |
if (!newVar.key || !newVar.value) { | |
return; | |
} | |
// Check for duplicate keys is no longer needed since Map handles this automatically | |
addVariable(newVar.key, newVar.value); | |
setNewVar({key: '', value: ''}); | |
}; | |
return ( | |
<Card | |
title={ | |
<span> | |
<SettingOutlined /> {t('Environment Variables')} | |
</span> | |
} | |
className={SessionStyles['interaction-tab-card']} | |
> | |
<div className={SessionStyles.container}> | |
<div className={SessionStyles.addForm}> | |
<Space.Compact style={{width: '100%'}}> | |
<Input | |
placeholder={t('Variable Name')} | |
value={newVar.key} | |
onChange={(e) => setNewVar({...newVar, key: e.target.value})} | |
/> | |
<Input.Password | |
placeholder={t('Value')} | |
value={newVar.value} | |
onChange={(e) => setNewVar({...newVar, value: e.target.value})} | |
/> | |
<Button | |
type="primary" | |
icon={<PlusOutlined />} | |
onClick={handleAddVariable} | |
disabled={!newVar.key || !newVar.value} | |
> | |
{t('Add')} | |
</Button> | |
</Space.Compact> | |
</div> | |
<Table | |
columns={columns} | |
dataSource={tableData} | |
pagination={false} | |
rowKey="key" | |
size="small" | |
/> | |
</div> | |
</Card> | |
); | |
}; | |
export default EnvironmentVariables; | |
import {DeleteOutlined, PlusOutlined} from '@ant-design/icons'; | |
import {Button, Col, Form, Input, Popconfirm, Row, Table, Tooltip} from 'antd'; | |
import {useState} from 'react'; | |
const EnvironmentVariables = ({t, envVars, addVariable, deleteVariable}) => { | |
const [newVar, setNewVar] = useState({key: '', value: ''}); | |
const tableData = Array.from(envVars, ([key, value]) => ({key, value})); | |
const columns = [ | |
{ | |
title: t('Variable Name'), | |
dataIndex: 'key', | |
key: 'key', | |
width: '45%', | |
}, | |
{ | |
title: t('Value'), | |
dataIndex: 'value', | |
key: 'value', | |
width: '45%', | |
render: (text) => <Input.Password value={text} readOnly />, | |
}, | |
{ | |
title: t('Actions'), | |
key: 'action', | |
width: '10%', | |
render: (_, record) => ( | |
<Tooltip zIndex={3} title={t('Delete')}> | |
<Popconfirm | |
zIndex={4} | |
title={t('confirmDeletion')} | |
placement="topRight" | |
okText={t('OK')} | |
cancelText={t('Cancel')} | |
onConfirm={() => deleteVariable(record.key)} | |
> | |
<Button icon={<DeleteOutlined />} /> | |
</Popconfirm> | |
</Tooltip> | |
), | |
}, | |
]; | |
const handleAddVariable = () => { | |
if (!newVar.key || !newVar.value) { | |
return; | |
} | |
// Check for duplicate keys is no longer needed since Map handles this automatically | |
addVariable(newVar.key, newVar.value); | |
setNewVar({key: '', value: ''}); | |
}; | |
return ( | |
<> | |
<Row gutter={8}> | |
<Col span={11}> | |
<Form.Item> | |
<Input | |
placeholder={t('Variable Name')} | |
value={newVar.key} | |
onChange={(e) => setNewVar({...newVar, key: e.target.value})} | |
/> | |
</Form.Item> | |
</Col> | |
<Col span={11}> | |
<Form.Item> | |
<Input.Password | |
placeholder={t('Value')} | |
value={newVar.value} | |
onChange={(e) => setNewVar({...newVar, value: e.target.value})} | |
/> | |
</Form.Item> | |
</Col> | |
<Col span={2}> | |
<Form.Item> | |
<Button | |
block | |
type="primary" | |
icon={<PlusOutlined />} | |
onClick={handleAddVariable} | |
disabled={!newVar.key || !newVar.value} | |
> | |
{t('Add')} | |
</Button> | |
</Form.Item> | |
</Col> | |
</Row> | |
<Table | |
columns={columns} | |
dataSource={tableData} | |
pagination={false} | |
sticky={true} | |
rowKey="key" | |
scroll={{y: 'calc(100vh - 34em)'}} | |
size="small" | |
/> | |
</> | |
); | |
}; | |
export default EnvironmentVariables; |
@@ -316,3 +316,19 @@ | |||
color: #ff4d4f; | |||
font-size: 12px; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you agree with my suggestion for components/Session/EnvironmentVariables.jsx
, all of these changes can be removed
Addressing #1702
Environment variable tab:
Usage:
"appium:testing": "${asdf}",
We will address export and import of the secrets in a different PR.