From a997b40852649f67622fa2bfb6527ac9345dee3f Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 28 Feb 2020 17:25:31 -0500 Subject: [PATCH 01/12] Refactors YamlJsonToggle component into something a little more generic so that it can be used to toggle between local and utc times in the schedule details view. --- .../CodeMirrorInput/VariablesDetail.jsx | 10 +++- .../CodeMirrorInput/VariablesDetail.test.jsx | 8 +-- .../CodeMirrorInput/VariablesField.jsx | 10 +++- .../CodeMirrorInput/VariablesInput.jsx | 56 ++++++------------- .../CodeMirrorInput/YamlJsonToggle.jsx | 44 --------------- .../ButtonGroup.jsx | 0 awx/ui_next/src/components/Toggle/Toggle.jsx | 52 +++++++++++++++++ awx/ui_next/src/components/Toggle/index.js | 1 + 8 files changed, 89 insertions(+), 92 deletions(-) delete mode 100644 awx/ui_next/src/components/CodeMirrorInput/YamlJsonToggle.jsx rename awx/ui_next/src/components/{CodeMirrorInput => Toggle}/ButtonGroup.jsx (100%) create mode 100644 awx/ui_next/src/components/Toggle/Toggle.jsx create mode 100644 awx/ui_next/src/components/Toggle/index.js diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx index 83533f1168..97634975b4 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx @@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react'; import { string, node, number } from 'prop-types'; import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core'; import { DetailName, DetailValue } from '@components/DetailList'; +import Toggle from '@components/Toggle'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import CodeMirrorInput from './CodeMirrorInput'; -import YamlJsonToggle from './YamlJsonToggle'; import { JSON_MODE, YAML_MODE } from './constants'; function getValueAsMode(value, mode) { @@ -50,8 +50,12 @@ function VariablesDetail({ value, label, rows }) { - { try { setCurrentValue(getValueAsMode(currentValue, newMode)); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx index 05548f8aa8..455b47b04b 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx @@ -31,12 +31,12 @@ describe('', () => { const wrapper = shallow( ); - wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); + wrapper.find('Toggle').invoke('onChange')('javascript'); const input = wrapper.find('Styled(CodeMirrorInput)'); expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}'); - wrapper.find('YamlJsonToggle').invoke('onChange')('yaml'); + wrapper.find('Toggle').invoke('onChange')('yaml'); const input2 = wrapper.find('Styled(CodeMirrorInput)'); expect(input2.prop('mode')).toEqual('yaml'); expect(input2.prop('value')).toEqual('foo: bar\n'); @@ -53,7 +53,7 @@ describe('', () => { ); act(() => { - wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); + wrapper.find('Toggle').invoke('onChange')('javascript'); }); wrapper.setProps({ value: '---bar: baz', @@ -73,7 +73,7 @@ describe('', () => { test('should default empty json to "{}"', () => { const wrapper = mount(); act(() => { - wrapper.find('YamlJsonToggle').invoke('onChange')('javascript'); + wrapper.find('Toggle').invoke('onChange')('javascript'); }); wrapper.setProps({ value: '' }); const input = wrapper.find('Styled(CodeMirrorInput)'); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index 336a3fa996..4ea07052ec 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -6,9 +6,9 @@ import { useField } from 'formik'; import styled from 'styled-components'; import { Split, SplitItem } from '@patternfly/react-core'; import { CheckboxField } from '@components/FormField'; +import Toggle from '@components/Toggle'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import CodeMirrorInput from './CodeMirrorInput'; -import YamlJsonToggle from './YamlJsonToggle'; import { JSON_MODE, YAML_MODE } from './constants'; const FieldHeader = styled.div` @@ -34,8 +34,12 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) { - { try { const newVal = diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx index faddd2bc13..785b05df6c 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx @@ -1,21 +1,16 @@ import React, { useState } from 'react'; import { string, func, bool, number } from 'prop-types'; -import { Button, Split, SplitItem } from '@patternfly/react-core'; +import { Split, SplitItem } from '@patternfly/react-core'; import styled from 'styled-components'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; +import Toggle from '@components/Toggle'; import CodeMirrorInput from './CodeMirrorInput'; -import ButtonGroup from './ButtonGroup'; import { JSON_MODE, YAML_MODE } from './constants'; function formatJson(jsonString) { return JSON.stringify(JSON.parse(jsonString), null, 2); } -const SmallButton = styled(Button)` - padding: 3px 8px; - font-size: var(--pf-global--FontSize--xs); -`; - const SplitItemRight = styled(SplitItem)` margin-bottom: 5px; `; @@ -47,40 +42,25 @@ function VariablesInput(props) { - - { - if (mode === YAML_MODE) { - return; - } - try { - onChange(jsonToYaml(value)); - setMode(YAML_MODE); - } catch (err) { - onError(err.message); - } - }} - variant={mode === YAML_MODE ? 'primary' : 'secondary'} - > - YAML - - { + { + try { if (mode === JSON_MODE) { - return; - } - try { + onChange(jsonToYaml(value)); + } else { onChange(yamlToJson(value)); - setMode(JSON_MODE); - } catch (err) { - onError(err.message); } - }} - variant={mode === JSON_MODE ? 'primary' : 'secondary'} - > - JSON - - + setMode(newMode); + } catch (err) { + onError(err.message); + } + }} + /> { - if (mode !== newMode) { - onChange(newMode); - } - }; - - return ( - - setMode(YAML_MODE)} - variant={mode === YAML_MODE ? 'primary' : 'secondary'} - > - YAML - - setMode(JSON_MODE)} - variant={mode === JSON_MODE ? 'primary' : 'secondary'} - > - JSON - - - ); -} -YamlJsonToggle.propTypes = { - mode: oneOf([YAML_MODE, JSON_MODE]).isRequired, - onChange: func.isRequired, -}; - -export default YamlJsonToggle; diff --git a/awx/ui_next/src/components/CodeMirrorInput/ButtonGroup.jsx b/awx/ui_next/src/components/Toggle/ButtonGroup.jsx similarity index 100% rename from awx/ui_next/src/components/CodeMirrorInput/ButtonGroup.jsx rename to awx/ui_next/src/components/Toggle/ButtonGroup.jsx diff --git a/awx/ui_next/src/components/Toggle/Toggle.jsx b/awx/ui_next/src/components/Toggle/Toggle.jsx new file mode 100644 index 0000000000..db0d796000 --- /dev/null +++ b/awx/ui_next/src/components/Toggle/Toggle.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { func, string } from 'prop-types'; +import styled from 'styled-components'; +import { Button } from '@patternfly/react-core'; +import ButtonGroup from './ButtonGroup'; + +const SmallButton = styled(Button)` + padding: 3px 8px; + font-size: var(--pf-global--FontSize--xs); +`; + +function Toggle({ + leftLabel, + leftMode, + rightLabel, + rightMode, + currentMode, + onChange, +}) { + const setValue = newValue => { + if (currentMode !== newValue) { + onChange(newValue); + } + }; + + return ( + + setValue(leftMode)} + variant={currentMode === leftMode ? 'primary' : 'secondary'} + > + {leftLabel} + + setValue(rightMode)} + variant={currentMode === rightMode ? 'primary' : 'secondary'} + > + {rightLabel} + + + ); +} +Toggle.propTypes = { + leftLabel: string.isRequired, + leftMode: string.isRequired, + rightLabel: string.isRequired, + rightMode: string.isRequired, + currentMode: string.isRequired, + onChange: func.isRequired, +}; + +export default Toggle; diff --git a/awx/ui_next/src/components/Toggle/index.js b/awx/ui_next/src/components/Toggle/index.js new file mode 100644 index 0000000000..c2ec545530 --- /dev/null +++ b/awx/ui_next/src/components/Toggle/index.js @@ -0,0 +1 @@ +export { default } from './Toggle'; From 1f0acef844203eac826d6db391a907b9277d986e Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 2 Mar 2020 11:22:18 -0500 Subject: [PATCH 02/12] Changes Toggle to MultiButtonToggle in an attempt to differentiate it from an upstream PF component. Altered props to be a bit more concise as well as support more than two buttons. --- .../CodeMirrorInput/VariablesDetail.jsx | 11 ++-- .../CodeMirrorInput/VariablesDetail.test.jsx | 8 +-- .../CodeMirrorInput/VariablesField.jsx | 11 ++-- .../CodeMirrorInput/VariablesInput.jsx | 11 ++-- .../ButtonGroup.jsx | 0 .../MultiButtonToggle/MultiButtonToggle.jsx | 66 +++++++++++++++++++ .../src/components/MultiButtonToggle/index.js | 1 + awx/ui_next/src/components/Toggle/Toggle.jsx | 52 --------------- awx/ui_next/src/components/Toggle/index.js | 1 - 9 files changed, 83 insertions(+), 78 deletions(-) rename awx/ui_next/src/components/{Toggle => MultiButtonToggle}/ButtonGroup.jsx (100%) create mode 100644 awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx create mode 100644 awx/ui_next/src/components/MultiButtonToggle/index.js delete mode 100644 awx/ui_next/src/components/Toggle/Toggle.jsx delete mode 100644 awx/ui_next/src/components/Toggle/index.js diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx index 97634975b4..768b85b14b 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { string, node, number } from 'prop-types'; import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core'; import { DetailName, DetailValue } from '@components/DetailList'; -import Toggle from '@components/Toggle'; +import MultiButtonToggle from '@components/MultiButtonToggle'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import CodeMirrorInput from './CodeMirrorInput'; import { JSON_MODE, YAML_MODE } from './constants'; @@ -50,12 +50,9 @@ function VariablesDetail({ value, label, rows }) { - { try { setCurrentValue(getValueAsMode(currentValue, newMode)); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx index 455b47b04b..5ffdaeb1e7 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.test.jsx @@ -31,12 +31,12 @@ describe('', () => { const wrapper = shallow( ); - wrapper.find('Toggle').invoke('onChange')('javascript'); + wrapper.find('MultiButtonToggle').invoke('onChange')('javascript'); const input = wrapper.find('Styled(CodeMirrorInput)'); expect(input.prop('mode')).toEqual('javascript'); expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}'); - wrapper.find('Toggle').invoke('onChange')('yaml'); + wrapper.find('MultiButtonToggle').invoke('onChange')('yaml'); const input2 = wrapper.find('Styled(CodeMirrorInput)'); expect(input2.prop('mode')).toEqual('yaml'); expect(input2.prop('value')).toEqual('foo: bar\n'); @@ -53,7 +53,7 @@ describe('', () => { ); act(() => { - wrapper.find('Toggle').invoke('onChange')('javascript'); + wrapper.find('MultiButtonToggle').invoke('onChange')('javascript'); }); wrapper.setProps({ value: '---bar: baz', @@ -73,7 +73,7 @@ describe('', () => { test('should default empty json to "{}"', () => { const wrapper = mount(); act(() => { - wrapper.find('Toggle').invoke('onChange')('javascript'); + wrapper.find('MultiButtonToggle').invoke('onChange')('javascript'); }); wrapper.setProps({ value: '' }); const input = wrapper.find('Styled(CodeMirrorInput)'); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index 4ea07052ec..c231a23db4 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -6,7 +6,7 @@ import { useField } from 'formik'; import styled from 'styled-components'; import { Split, SplitItem } from '@patternfly/react-core'; import { CheckboxField } from '@components/FormField'; -import Toggle from '@components/Toggle'; +import MultiButtonToggle from '@components/MultiButtonToggle'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import CodeMirrorInput from './CodeMirrorInput'; import { JSON_MODE, YAML_MODE } from './constants'; @@ -34,12 +34,9 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) { - { try { const newVal = diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx index 785b05df6c..2e4560d720 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx @@ -3,7 +3,7 @@ import { string, func, bool, number } from 'prop-types'; import { Split, SplitItem } from '@patternfly/react-core'; import styled from 'styled-components'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; -import Toggle from '@components/Toggle'; +import MultiButtonToggle from '@components/MultiButtonToggle'; import CodeMirrorInput from './CodeMirrorInput'; import { JSON_MODE, YAML_MODE } from './constants'; @@ -42,12 +42,9 @@ function VariablesInput(props) { - { try { if (mode === JSON_MODE) { diff --git a/awx/ui_next/src/components/Toggle/ButtonGroup.jsx b/awx/ui_next/src/components/MultiButtonToggle/ButtonGroup.jsx similarity index 100% rename from awx/ui_next/src/components/Toggle/ButtonGroup.jsx rename to awx/ui_next/src/components/MultiButtonToggle/ButtonGroup.jsx diff --git a/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx new file mode 100644 index 0000000000..460313bbca --- /dev/null +++ b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { func, string } from 'prop-types'; +import styled from 'styled-components'; +import { Button } from '@patternfly/react-core'; +import ButtonGroup from './ButtonGroup'; + +const SmallButton = styled(Button)` + padding: 3px 8px; + font-size: var(--pf-global--FontSize--xs); +`; + +function MultiButtonToggle({ buttons, currentValue, onChange }) { + const setValue = newValue => { + if (currentValue !== newValue) { + onChange(newValue); + } + }; + + return ( + + {buttons.map(([value, label]) => ( + setValue(value)} + variant={currentValue === value ? 'primary' : 'secondary'} + > + {label} + + ))} + + ); +} + +const buttonsPropType = { + isRequired: ({ buttons }) => { + if (!buttons) { + return new Error( + `The prop buttons is marked as required in MultiButtonToggle, but its value is '${buttons}'` + ); + } + // We expect this data structure to look like: + // [[value(unrestricted type), label(string)], [value(unrestricted type), label(string)], ...] + if ( + !Array.isArray(buttons) || + buttons.length < 2 || + buttons.reduce( + (prevVal, button) => prevVal || typeof button[1] !== 'string', + false + ) + ) { + return new Error( + `Invalid prop buttons supplied to MultiButtonToggle. Validation failed.` + ); + } + + return null; + }, +}; + +MultiButtonToggle.propTypes = { + buttons: buttonsPropType.isRequired, + currentValue: string.isRequired, + onChange: func.isRequired, +}; + +export default MultiButtonToggle; diff --git a/awx/ui_next/src/components/MultiButtonToggle/index.js b/awx/ui_next/src/components/MultiButtonToggle/index.js new file mode 100644 index 0000000000..9332082446 --- /dev/null +++ b/awx/ui_next/src/components/MultiButtonToggle/index.js @@ -0,0 +1 @@ +export { default } from './MultiButtonToggle'; diff --git a/awx/ui_next/src/components/Toggle/Toggle.jsx b/awx/ui_next/src/components/Toggle/Toggle.jsx deleted file mode 100644 index db0d796000..0000000000 --- a/awx/ui_next/src/components/Toggle/Toggle.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { func, string } from 'prop-types'; -import styled from 'styled-components'; -import { Button } from '@patternfly/react-core'; -import ButtonGroup from './ButtonGroup'; - -const SmallButton = styled(Button)` - padding: 3px 8px; - font-size: var(--pf-global--FontSize--xs); -`; - -function Toggle({ - leftLabel, - leftMode, - rightLabel, - rightMode, - currentMode, - onChange, -}) { - const setValue = newValue => { - if (currentMode !== newValue) { - onChange(newValue); - } - }; - - return ( - - setValue(leftMode)} - variant={currentMode === leftMode ? 'primary' : 'secondary'} - > - {leftLabel} - - setValue(rightMode)} - variant={currentMode === rightMode ? 'primary' : 'secondary'} - > - {rightLabel} - - - ); -} -Toggle.propTypes = { - leftLabel: string.isRequired, - leftMode: string.isRequired, - rightLabel: string.isRequired, - rightMode: string.isRequired, - currentMode: string.isRequired, - onChange: func.isRequired, -}; - -export default Toggle; diff --git a/awx/ui_next/src/components/Toggle/index.js b/awx/ui_next/src/components/Toggle/index.js deleted file mode 100644 index c2ec545530..0000000000 --- a/awx/ui_next/src/components/Toggle/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Toggle'; From 322b4ee1e4a63fd4eb89475103e8dc0fd5c4cfc1 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 2 Mar 2020 13:43:15 -0500 Subject: [PATCH 03/12] Updates our four patternfly deps to latest --- awx/ui_next/package-lock.json | 98 +++++++++++++++++------------------ awx/ui_next/package.json | 8 +-- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 941399b8f9..75edf45828 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -4135,36 +4135,51 @@ "dev": true }, "@patternfly/patternfly": { - "version": "2.56.3", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.56.3.tgz", - "integrity": "sha512-merUreEz4ul84s+OXwJ27AtMtcBdzExDX+Xn/T84OD6OAgG8iU1x1dnWdFBeEFKOlTpKxwjXxS/Zc8tSxpfRBw==" + "version": "2.66.0", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.66.0.tgz", + "integrity": "sha512-fZMr2q9LZhVtKAEcDJ4rzcCGC6iN93mEQPoLlv2T9td5Hba1bLw8Bpgp5fdTm95Fv/++AY0PsdUPZUzh1cx7Sg==" }, "@patternfly/react-core": { - "version": "3.135.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.135.0.tgz", - "integrity": "sha512-5UotimA2VUiYWt/v8j4x/z8MMyvbgXi7z7BKPzicIiKGwmREJCgQ+kf0eAqGUg/YjFLKcDKncoBrPNqVZ8Ykpg==", + "version": "3.140.11", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.140.11.tgz", + "integrity": "sha512-841DeN5BTuUS02JfVXAAVJYtWY0HWc4ewqMD32Xog2MAR/pn74jzjnQOSQr4LUyVrH5QufB68SK4Alm2+IUzSw==", "requires": { - "@patternfly/react-icons": "^3.14.39", - "@patternfly/react-styles": "^3.6.27", - "@patternfly/react-tokens": "^2.7.25", + "@patternfly/react-icons": "^3.15.3", + "@patternfly/react-styles": "^3.7.4", + "@patternfly/react-tokens": "^2.8.4", "emotion": "^9.2.9", "exenv": "^1.2.2", "focus-trap-react": "^4.0.1", "tippy.js": "5.1.2" + }, + "dependencies": { + "@patternfly/react-icons": { + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.15.4.tgz", + "integrity": "sha512-tOVirISoZDIn0bWYFctGN9B7Q8wQ19FaK4XIUD2sgIDRBzDbe9JWuqdef7ogJFF78eQnZNsWOci6nhvVCVF/zA==", + "requires": { + "@fortawesome/free-brands-svg-icons": "^5.8.1" + } + }, + "@patternfly/react-tokens": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.8.4.tgz", + "integrity": "sha512-GlLyutls0bG39Nwl/sv2FUkicwyRNrXQFso+e7Y4470+VOUtSsVSdQz+rTjgPxQ38olKPsSZdtEjqN9o2PbDiw==" + } } }, "@patternfly/react-icons": { - "version": "3.14.39", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.14.39.tgz", - "integrity": "sha512-/1hhKEFRtvBYNa8BFRurqHdlUYYzdovmllwtEWcxye5lffDC1Ghco1NGQzjm0FtzkxX1hPFvw04HRR2jBBG8xQ==", + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.15.4.tgz", + "integrity": "sha512-tOVirISoZDIn0bWYFctGN9B7Q8wQ19FaK4XIUD2sgIDRBzDbe9JWuqdef7ogJFF78eQnZNsWOci6nhvVCVF/zA==", "requires": { "@fortawesome/free-brands-svg-icons": "^5.8.1" } }, "@patternfly/react-styles": { - "version": "3.6.27", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.6.27.tgz", - "integrity": "sha512-XGI+lR0/QIqkGnGHcnzbynR9JImTftDztsXBgaB0FoDu/DmPY4EABgtsF9HvfoAjsIg8KxRs32tqoXN3yN0TWg==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.7.4.tgz", + "integrity": "sha512-D+wu0OIfWVgxWNShQhTK9cadw+KdMCoBYR8gbWjV9Q1aCsCEV/aL/x1nMyyaUQ3c2dqizHhujDG4z9jUZCmCcw==", "requires": { "camel-case": "^3.0.0", "css": "^2.2.3", @@ -4174,9 +4189,9 @@ } }, "@patternfly/react-tokens": { - "version": "2.7.25", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.7.25.tgz", - "integrity": "sha512-04hRDWt07pyjLUO1VN9QbrPpQMJzjd+nQYp8vgoe6+mYBzw+D4banJeudZ1oTFii9hWV+mLEu6aiwPtTigPM1Q==" + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.8.4.tgz", + "integrity": "sha512-GlLyutls0bG39Nwl/sv2FUkicwyRNrXQFso+e7Y4470+VOUtSsVSdQz+rTjgPxQ38olKPsSZdtEjqN9o2PbDiw==" }, "@types/babel__core": { "version": "7.1.1", @@ -7047,9 +7062,9 @@ } }, "csstype": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", - "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", + "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" }, "currently-unhandled": { "version": "0.4.1", @@ -9367,8 +9382,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -9389,14 +9403,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9411,20 +9423,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -9541,8 +9550,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -9554,7 +9562,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9569,7 +9576,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -9577,14 +9583,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -9603,7 +9607,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -9684,8 +9687,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -9697,7 +9699,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -9783,8 +9784,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -9820,7 +9820,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9840,7 +9839,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9884,14 +9882,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index bc844ca9e4..e934301d13 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -58,10 +58,10 @@ }, "dependencies": { "@lingui/react": "^2.7.2", - "@patternfly/patternfly": "^2.56.3", - "@patternfly/react-core": "^3.135.0", - "@patternfly/react-icons": "^3.14.39", - "@patternfly/react-tokens": "^2.7.25", + "@patternfly/patternfly": "^2.66.0", + "@patternfly/react-core": "^3.140.11", + "@patternfly/react-icons": "^3.15.4", + "@patternfly/react-tokens": "^2.8.4", "ansi-to-html": "^0.6.11", "axios": "^0.18.1", "codemirror": "^5.47.0", From 6207dad226aff87e67df477d98df6f5040904b7d Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 3 Mar 2020 12:14:22 -0500 Subject: [PATCH 04/12] fix broken grafana notifications since the custom notification template refactor, grafana notification support has been broken; this is largely because grafana functions more like the webhooks, and needs to send JSON in its notification body see: https://github.com/ansible/awx/issues/6137 --- awx/api/views/__init__.py | 2 +- awx/main/notifications/grafana_backend.py | 53 +++++++++++++++++++---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 34fd367970..0e937da67c 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -4303,7 +4303,7 @@ class NotificationTemplateTest(GenericAPIView): msg = "Tower Notification Test {} {}".format(obj.id, settings.TOWER_URL_BASE) if obj.notification_type in ('email', 'pagerduty'): body = "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE) - elif obj.notification_type == 'webhook': + elif obj.notification_type in ('webhook', 'grafana'): body = '{{"body": "Ansible Tower Test Notification {} {}"}}'.format(obj.id, settings.TOWER_URL_BASE) else: body = {"body": "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE)} diff --git a/awx/main/notifications/grafana_backend.py b/awx/main/notifications/grafana_backend.py index 58137f27aa..8fbedf58d3 100644 --- a/awx/main/notifications/grafana_backend.py +++ b/awx/main/notifications/grafana_backend.py @@ -2,6 +2,7 @@ # All Rights Reserved. import datetime +import json import logging import requests import dateutil.parser as dp @@ -23,6 +24,33 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase): recipient_parameter = "grafana_url" sender_parameter = None + DEFAULT_BODY = "{{ job_metadata }}" + default_messages = { + "started": { + "body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG + }, + "success": { + "body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG + }, + "error": { + "body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG + }, + "workflow_approval": { + "running": { + "message": CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_MSG, "body": None + }, + "approved": { + "message": CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_MSG, "body": None + }, + "timed_out": { + "message": CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_MSG, "body": None + }, + "denied": { + "message": CustomNotificationBase.DEFAULT_APPROVAL_DENIED_MSG, "body": None + } + } + } + def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True, fail_silently=False, **kwargs): super(GrafanaBackend, self).__init__(fail_silently=fail_silently) @@ -34,6 +62,13 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase): self.isRegion = isRegion def format_body(self, body): + # expect body to be a string representing a dict + try: + potential_body = json.loads(body) + if isinstance(potential_body, dict): + body = potential_body + except json.JSONDecodeError: + body = {} return body def send_messages(self, messages): @@ -41,14 +76,16 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase): for m in messages: grafana_data = {} grafana_headers = {} - try: - epoch=datetime.datetime.utcfromtimestamp(0) - grafana_data['time'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000) - grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000) - except ValueError: - logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) - if not self.fail_silently: - raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) + if 'started' in m.body: + try: + epoch=datetime.datetime.utcfromtimestamp(0) + grafana_data['time'] = grafana_data['timeEnd'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000) + if m.body.get('finished'): + grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000) + except ValueError: + logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) + if not self.fail_silently: + raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) grafana_data['isRegion'] = self.isRegion grafana_data['dashboardId'] = self.dashboardId grafana_data['panelId'] = self.panelId From 3c7f5962881473736dc6c848c172da483dcdacf9 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 3 Mar 2020 12:57:12 -0500 Subject: [PATCH 05/12] Change currentValue prop to simply value. Adds basic unit test coverage to MultiButtonToggle component. --- .../CodeMirrorInput/VariablesDetail.jsx | 2 +- .../CodeMirrorInput/VariablesField.jsx | 2 +- .../CodeMirrorInput/VariablesInput.jsx | 2 +- .../MultiButtonToggle/MultiButtonToggle.jsx | 25 ++++++++------- .../MultiButtonToggle.test.jsx | 31 +++++++++++++++++++ 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.test.jsx diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx index 768b85b14b..f561a6f74e 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx @@ -52,7 +52,7 @@ function VariablesDetail({ value, label, rows }) { { try { setCurrentValue(getValueAsMode(currentValue, newMode)); diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index c231a23db4..d27b85d328 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -36,7 +36,7 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) { { try { const newVal = diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx index 2e4560d720..cf7758b53e 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesInput.jsx @@ -44,7 +44,7 @@ function VariablesInput(props) { { try { if (mode === JSON_MODE) { diff --git a/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx index 460313bbca..9c72cfbab3 100644 --- a/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx +++ b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.jsx @@ -9,24 +9,25 @@ const SmallButton = styled(Button)` font-size: var(--pf-global--FontSize--xs); `; -function MultiButtonToggle({ buttons, currentValue, onChange }) { +function MultiButtonToggle({ buttons, value, onChange }) { const setValue = newValue => { - if (currentValue !== newValue) { + if (value !== newValue) { onChange(newValue); } }; return ( - {buttons.map(([value, label]) => ( - setValue(value)} - variant={currentValue === value ? 'primary' : 'secondary'} - > - {label} - - ))} + {buttons && + buttons.map(([buttonValue, buttonLabel]) => ( + setValue(buttonValue)} + variant={buttonValue === value ? 'primary' : 'secondary'} + > + {buttonLabel} + + ))} ); } @@ -59,7 +60,7 @@ const buttonsPropType = { MultiButtonToggle.propTypes = { buttons: buttonsPropType.isRequired, - currentValue: string.isRequired, + value: string.isRequired, onChange: func.isRequired, }; diff --git a/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.test.jsx b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.test.jsx new file mode 100644 index 0000000000..15c65e3a29 --- /dev/null +++ b/awx/ui_next/src/components/MultiButtonToggle/MultiButtonToggle.test.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import MultiButtonToggle from './MultiButtonToggle'; + +describe('', () => { + let wrapper; + const onChange = jest.fn(); + beforeAll(() => { + wrapper = mount( + + ); + }); + afterAll(() => { + wrapper.unmount(); + }); + it('should render buttons successfully', () => { + const buttons = wrapper.find('Button'); + expect(buttons.length).toBe(2); + expect(buttons.at(0).props().variant).toBe('primary'); + expect(buttons.at(1).props().variant).toBe('secondary'); + }); + it('should call onChange function when button clicked', () => { + const buttons = wrapper.find('Button'); + buttons.at(1).simulate('click'); + expect(onChange).toHaveBeenCalledWith('json'); + }); +}); From d5372dae36c89d436211c4027a9ffbc84dea0d58 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 3 Mar 2020 13:07:03 -0500 Subject: [PATCH 06/12] Upgrade react and react-dom to latest --- awx/ui_next/package-lock.json | 20 ++++++++++---------- awx/ui_next/package.json | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 75edf45828..7c8b645522 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -15370,9 +15370,9 @@ } }, "react": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz", - "integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz", + "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -15385,20 +15385,20 @@ "integrity": "sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag==" }, "react-dom": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz", - "integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.0.tgz", + "integrity": "sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.16.2" + "scheduler": "^0.19.0" }, "dependencies": { "scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.0.tgz", + "integrity": "sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index e934301d13..cdd79cd2d7 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -72,9 +72,9 @@ "html-entities": "^1.2.1", "js-yaml": "^3.13.1", "prop-types": "^15.6.2", - "react": "^16.10.2", + "react": "^16.13.0", "react-codemirror2": "^6.0.0", - "react-dom": "^16.10.2", + "react-dom": "^16.13.0", "react-router-dom": "^5.1.2", "react-virtualized": "^9.21.1", "styled-components": "^4.2.0" From 4a6db13daaa5b4814d97a0197532420279cdb753 Mon Sep 17 00:00:00 2001 From: Caleb Boylan Date: Fri, 28 Feb 2020 12:36:32 -0800 Subject: [PATCH 07/12] Copy collection integration tests in --- .../targets/tower_credential/tasks/main.yml | 538 ++++++++++++++++++ .../tower_credential_type/tasks/main.yml | 23 + .../targets/tower_inventory/tasks/main.yml | 78 +++ .../targets/tower_job_cancel/tasks/main.yml | 39 ++ .../targets/tower_job_launch/tasks/main.yml | 101 ++++ .../targets/tower_job_list/tasks/main.yml | 37 ++ .../targets/tower_job_wait/tasks/main.yml | 24 + .../targets/tower_label/tasks/main.yml | 19 + .../targets/tower_notification/tasks/main.yml | 205 +++++++ .../targets/tower_organization/tasks/main.yml | 122 ++++ .../tasks/create_project_dir.yml | 45 ++ .../targets/tower_receive/tasks/main.yml | 17 + .../targets/tower_role/tasks/main.yml | 39 ++ .../targets/tower_send/tasks/main.yml | 80 +++ .../targets/tower_settings/tasks/main.yml | 77 +++ .../targets/tower_team/tasks/main.yml | 48 ++ .../targets/tower_user/tasks/main.yml | 94 +++ .../tower_workflow_template/tasks/main.yml | 81 +++ 18 files changed, 1667 insertions(+) create mode 100644 awx_collection/tests/integration/targets/tower_credential/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_label/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_notification/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_organization/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml create mode 100644 awx_collection/tests/integration/targets/tower_receive/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_role/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_send/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_settings/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_team/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_user/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml diff --git a/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml new file mode 100644 index 0000000000..20631bfdd0 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml @@ -0,0 +1,538 @@ +- name: create a tempdir for an SSH key + local_action: shell mktemp -d + register: tempdir + +- name: Generate a local SSH key + local_action: "shell ssh-keygen -b 2048 -t rsa -f {{ tempdir.stdout }}/id_rsa -q -N 'passphrase'" + +- name: Read the generated key + set_fact: + ssh_key_data: "{{ lookup('file', tempdir.stdout + '/id_rsa') }}" + +- name: Create a User-specific credential + tower_credential: + name: SSH Credential + organization: Default + user: admin + state: present + kind: ssh + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a User-specific credential + tower_credential: + name: SSH Credential + organization: Default + user: admin + state: absent + kind: ssh + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid SSH credential + tower_credential: + name: SSH Credential + organization: Default + state: present + kind: ssh + description: An example SSH credential + username: joe + password: secret + become_method: sudo + become_username: superuser + become_password: supersecret + ssh_key_data: "{{ ssh_key_data }}" + ssh_key_unlock: "passphrase" + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid SSH credential from lookup source + tower_credential: + name: SSH Credential from lookup source + organization: Default + state: present + kind: ssh + description: An example SSH credential from lookup source + username: joe + password: secret + become_method: sudo + become_username: superuser + become_password: supersecret + ssh_key_data: "{{ lookup('file', tempdir.stdout + '/id_rsa') }}" + ssh_key_unlock: "passphrase" + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid SSH credential from file source + tower_credential: + name: SSH Credential from file source + organization: Default + state: present + kind: ssh + description: An example SSH credential from file source + username: joe + password: secret + become_method: sudo + become_username: superuser + become_password: supersecret + ssh_key_data: "{{ tempdir.stdout }}/id_rsa" + ssh_key_unlock: "passphrase" + register: result + +- assert: + that: + - "result is changed" + - "result is not failed" + - "'ssh_key_data should be a string, not a path to a file.' in result.deprecations[0].msg" + +- name: Create an invalid SSH credential (passphrase required) + tower_credential: + name: SSH Credential + organization: Default + state: present + kind: ssh + username: joe + ssh_key_data: "{{ ssh_key_data }}" + ignore_errors: yes + register: result + +- assert: + that: + - "result is failed" + - "'must be set when SSH key is encrypted' in result.msg" + +- name: Create an invalid SSH credential (Organization not found) + tower_credential: + name: SSH Credential + organization: Missing Organization + state: present + kind: ssh + username: joe + ignore_errors: yes + register: result + +- assert: + that: + - "result is failed" + - "'The requested object could not be found' in result.msg" + +- name: Delete an SSH credential + tower_credential: + name: SSH Credential + organization: Default + state: absent + kind: ssh + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Vault credential + tower_credential: + name: Vault Credential + organization: Default + state: present + kind: vault + description: An example Vault credential + vault_password: secret-vault + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Vault credential w/ kind=ssh (deprecated) + tower_credential: + name: Vault Credential + organization: Default + state: present + kind: ssh + description: An example Vault credential + vault_password: secret-vault + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Vault credential + tower_credential: + name: Vault Credential + organization: Default + state: absent + kind: vault + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Network credential + tower_credential: + name: Network Credential + organization: Default + state: present + kind: net + username: joe + password: secret + authorize: true + authorize_password: authorize-me + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Network credential + tower_credential: + name: Network Credential + organization: Default + state: absent + kind: net + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid SCM credential + tower_credential: + name: SCM Credential + organization: Default + state: present + kind: scm + username: joe + password: secret + ssh_key_data: "{{ ssh_key_data }}" + ssh_key_unlock: "passphrase" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an SCM credential + tower_credential: + name: SCM Credential + organization: Default + state: absent + kind: scm + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid AWS credential + tower_credential: + name: AWS Credential + organization: Default + state: present + kind: aws + username: joe + password: secret + security_token: aws-token + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an AWS credential + tower_credential: + name: AWS Credential + organization: Default + state: absent + kind: aws + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid VMWare credential + tower_credential: + name: VMWare Credential + organization: Default + state: present + kind: vmware + host: https://example.org + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an VMWare credential + tower_credential: + name: VMWare Credential + organization: Default + state: absent + kind: vmware + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Satellite6 credential + tower_credential: + name: Satellite6 Credential + organization: Default + state: present + kind: satellite6 + host: https://example.org + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Satellite6 credential + tower_credential: + name: Satellite6 Credential + organization: Default + state: absent + kind: satellite6 + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid CloudForms credential + tower_credential: + name: CloudForms Credential + organization: Default + state: present + kind: cloudforms + host: https://example.org + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a CloudForms credential + tower_credential: + name: CloudForms Credential + organization: Default + state: absent + kind: cloudforms + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid GCE credential + tower_credential: + name: GCE Credential + organization: Default + state: present + kind: gce + username: joe + project: ABC123 + ssh_key_data: "{{ ssh_key_data }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a GCE credential + tower_credential: + name: GCE Credential + organization: Default + state: absent + kind: gce + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid AzureRM credential + tower_credential: + name: AzureRM Credential + organization: Default + state: present + kind: azure_rm + username: joe + password: secret + subscription: some-subscription + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid AzureRM credential with a tenant + tower_credential: + name: AzureRM Credential + organization: Default + state: present + kind: azure_rm + client: some-client + secret: some-secret + tenant: some-tenant + subscription: some-subscription + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an AzureRM credential + tower_credential: + name: AzureRM Credential + organization: Default + state: absent + kind: azure_rm + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid OpenStack credential + tower_credential: + name: OpenStack Credential + organization: Default + state: present + kind: openstack + host: https://keystone.example.org + username: joe + password: secret + project: tenant123 + domain: some-domain + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a OpenStack credential + tower_credential: + name: OpenStack Credential + organization: Default + state: absent + kind: openstack + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid RHV credential + tower_credential: + name: RHV Credential + organization: Default + state: present + kind: rhv + host: https://example.org + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an RHV credential + tower_credential: + name: RHV Credential + organization: Default + state: absent + kind: rhv + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Insights credential + tower_credential: + name: Insights Credential + organization: Default + state: present + kind: insights + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an Insights credential + tower_credential: + name: Insights Credential + organization: Default + state: absent + kind: insights + register: result + +- assert: + that: + - "result is changed" + +- name: Create a valid Tower-to-Tower credential + tower_credential: + name: Tower Credential + organization: Default + state: present + kind: tower + host: https://tower.example.org + username: joe + password: secret + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Tower-to-Tower credential + tower_credential: + name: Tower Credential + organization: Default + state: absent + kind: tower + register: result + +- assert: + that: + - "result is changed" + +- name: Check module fails with correct msg + tower_credential: + name: test-credential + description: Credential Description + kind: ssh + organization: test-non-existing-org + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update credential, organization not found: The requested object could not be found.'" diff --git a/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml new file mode 100644 index 0000000000..c2d3edd377 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Add Tower credential type + tower_credential_type: + description: Credential type for Test + name: test-credential-type + kind: cloud + inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": True, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]} + injectors: {"extra_vars": {"test": "foo"}} + register: result + +- assert: + that: + - "result is changed" + +- name: Remove a Tower credential type + tower_credential_type: + name: test-credential-type + state: absent + register: result + +- assert: + that: + - "result is changed" diff --git a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml new file mode 100644 index 0000000000..bbbed0c81d --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml @@ -0,0 +1,78 @@ +- name: Clean up any pre-existing test Inventory + tower_inventory: + name: my-inventory + organization: Default + state: absent + ignore_errors: True + + +- name: Create an Inventory + tower_inventory: + name: my-inventory + organization: Default + state: present + register: result + +- assert: + that: + - "result is changed" + +- name: Test Inventory module idempotency + tower_inventory: + name: my-inventory + organization: Default + state: present + register: result + +- assert: + that: + - "result is not changed" + +- name: Fail Change Regular to Smart + tower_inventory: + name: my-inventory + organization: Default + kind: smart + register: result + ignore_errors: True + +- assert: + that: + - "result is failed" + +- name: Delete an Inventory + tower_inventory: + name: my-inventory + organization: Default + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Non-Existent Inventory + tower_inventory: + name: my-inventory + organization: Default + state: absent + register: result + +- assert: + that: + - "result is not changed" + +- name: Check module fails with correct msg + tower_inventory: + name: test-inventory + description: Inventory Description + organization: test-non-existing-org + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result is not changed" + - "result.msg =='Failed to update inventory, organization not found: The requested object could not be found.' + or result.msg =='The organizations test-non-existing-org was not found on the Tower server'" diff --git a/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml new file mode 100644 index 0000000000..4f7550f276 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml @@ -0,0 +1,39 @@ +- name: Launch a Job Template + tower_job_launch: + job_template: "Demo Job Template" + register: job + +- assert: + that: + - "job is changed" + +- name: Cancel the job + tower_job_cancel: + job_id: "{{ job.id }}" + register: results + +- assert: + that: + - results is changed + +- name: Cancel an already canceled job (assert failure) + tower_job_cancel: + job_id: "{{ job.id }}" + fail_if_not_running: True + register: results + ignore_errors: True + +- assert: + that: + - results is failed + +- name: Check module fails with correct msg + tower_job_cancel: + job_id: 9999999999 + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Unable to cancel job_id/9999999999: The requested object could not be found.' + or result.msg =='Unable to find job with id 9999999999'" diff --git a/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml new file mode 100644 index 0000000000..7436710e91 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml @@ -0,0 +1,101 @@ +- name: Launch a Job Template + tower_job_launch: + job_template: "Demo Job Template" + register: result + +- assert: + that: + - "result is changed" + - "result.status == 'pending'" + +- name: Wait for a job template to complete + tower_job_wait: + job_id: "{{ result.id }}" + max_interval: 10 + timeout: 120 + register: result + +- assert: + that: + - "result is not changed" + - "result.status == 'successful'" + +- name: Check module fails with correct msg + tower_job_launch: + job_template: "Non Existing Job Template" + inventory: "Test Inventory" + credential: "Test Credential" + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Unable to launch job, job_template/Non Existing Job Template was not found: The requested object could not be found.' + or result.msg == 'The inventories Test Inventory was not found on the Tower server'" + +- name: Create a Job Template for testing prompt on launch + tower_job_template: + name: "Demo Job Template - ask inventory and credential" + project: Demo Project + playbook: hello_world.yml + job_type: run + ask_credential: yes + ask_inventory: yes + state: present + register: result + +- name: Launch job template with inventory and credential for prompt on launch + tower_job_launch: + job_template: "Demo Job Template - ask inventory and credential" + inventory: "Demo Inventory" + credential: "Demo Credential" + register: result + +- assert: + that: + - "result is changed" + - "result.status == 'pending'" + +- name: Create a project for testing extra_vars + tower_project: + name: test-playbooks + organization: Default + scm_type: git + scm_url: https://github.com/ansible/test-playbooks + +- name: Create a Job Template for testing extra_vars + tower_job_template: + name: "Demo Job Template - extra_vars" + project: test-playbooks + playbook: debug.yml + job_type: run + state: present + inventory: "Demo Inventory" + extra_vars: + foo: bar + register: result + +- name: Launch job template with inventory and credential for prompt on launch + tower_job_launch: + job_template: "Demo Job Template - extra_vars" + register: result + +- assert: + that: + - "result is changed" + +- name: Get the job + tower_job_list: + query: {"id": "{{result.id}}" } + register: result + +- assert: + that: + - '{"foo": "bar"} | to_json in result.results[0].extra_vars' + +- name: Delete the job + tower_project: + name: "Demo Job Template - extra_vars" + organization: Default + state: absent + ignore_errors: True diff --git a/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml new file mode 100644 index 0000000000..47317c0919 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml @@ -0,0 +1,37 @@ +- name: Launch a Job Template + tower_job_launch: + job_template: "Demo Job Template" + register: job + +- assert: + that: + - "job is changed" + - "job.status == 'pending'" + +- name: List jobs w/ a matching primary key + tower_job_list: + query: {"id": "{{ job.id }}"} + register: matching_jobs + +- assert: + that: + - "{{ matching_jobs.count }} == 1" + +- name: List failed jobs (which don't exist) + tower_job_list: + status: failed + query: {"id": "{{ job.id }}"} + register: successful_jobs + +- assert: + that: + - "{{ successful_jobs.count }} == 0" + +- name: Get ALL result pages! + tower_job_list: + all_pages: True + register: all_page_query + +- assert: + that: + - 'not all_page_query.next' diff --git a/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml new file mode 100644 index 0000000000..0b46f00f7d --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml @@ -0,0 +1,24 @@ +- name: Launch a Job Template + tower_job_launch: + job_template: "Demo Job Template" + register: job + +- assert: + that: + - "job is changed" + - "job.status == 'pending'" + +- name: Wait for the Job to finish + tower_job_wait: + job_id: "{{ job.id }}" + timeout: 60 + +- name: Check module fails with correct msg + tower_job_wait: + job_id: "99999999" + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Unable to wait, no job_id 99999999 found: The requested object could not be found.'" diff --git a/awx_collection/tests/integration/targets/tower_label/tasks/main.yml b/awx_collection/tests/integration/targets/tower_label/tasks/main.yml new file mode 100644 index 0000000000..f86712b96a --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_label/tasks/main.yml @@ -0,0 +1,19 @@ +- name: Create a Label + tower_label: + name: important + organization: Default + state: present + +- name: Check module fails with correct msg + tower_label: + name: "Test Label" + organization: "Non existing org" + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result.msg == 'Failed to update label, organization not found: The requested object could not be found.'" + +# TODO: Deleting labels doesn't seem to work currently diff --git a/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml b/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml new file mode 100644 index 0000000000..4fff1ae6f9 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml @@ -0,0 +1,205 @@ +- name: Create Slack notification + tower_notification: + name: notification1 + organization: Default + notification_type: slack + token: a_token + channels: + - general + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete Slack notification + tower_notification: + name: notification1 + organization: Default + notification_type: slack + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add webhook notification + tower_notification: + name: notification2 + organization: Default + notification_type: webhook + url: http://www.example.com/hook + headers: + X-Custom-Header: value123 + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete webhook notification + tower_notification: + name: notification2 + organization: Default + notification_type: webhook + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add email notification + tower_notification: + name: notification3 + organization: Default + notification_type: email + username: user + password: s3cr3t + sender: tower@example.com + recipients: + - user1@example.com + host: smtp.example.com + port: 25 + use_tls: no + use_ssl: no + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete email notification + tower_notification: + name: notification3 + organization: Default + notification_type: email + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add twilio notification + tower_notification: + name: notification4 + organization: Default + notification_type: twilio + account_token: a_token + account_sid: a_sid + from_number: '+15551112222' + to_numbers: + - '+15553334444' + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete twilio notification + tower_notification: + name: notification4 + organization: Default + notification_type: twilio + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add PagerDuty notification + tower_notification: + name: notification5 + organization: Default + notification_type: pagerduty + token: a_token + subdomain: sub + client_name: client + service_key: a_key + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete PagerDuty notification + tower_notification: + name: notification5 + organization: Default + notification_type: pagerduty + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add HipChat notification + tower_notification: + name: notification6 + organization: Default + notification_type: hipchat + token: a_token + message_from: user1 + api_url: https://hipchat.example.com + color: red + rooms: + - room-A + notify: yes + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete HipChat notification + tower_notification: + name: notification6 + organization: Default + notification_type: hipchat + state: absent + register: result + +- assert: + that: + - result is changed + +- name: Add IRC notification + tower_notification: + name: notification7 + organization: Default + notification_type: irc + nickname: tower + password: s3cr3t + targets: + - user1 + port: 8080 + server: irc.example.com + use_ssl: no + state: present + register: result + +- assert: + that: + - result is changed + +- name: Delete IRC notification + tower_notification: + name: notification7 + organization: Default + notification_type: irc + state: absent + register: result + +- assert: + that: + - result is changed diff --git a/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml new file mode 100644 index 0000000000..c5577ce9e3 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml @@ -0,0 +1,122 @@ +- name: Generate an org name + set_fact: + org_name: "org-{{ lookup('randstr') }}" + +- name: Make sure {{ org_name }} is not there + tower_organization: + name: "{{ org_name }}" + state: absent + register: result + +- name: "Create a new organization" + tower_organization: + name: "{{ org_name }}" + register: result + +- assert: + that: "result is changed" + +- name: "Make sure making the same org is not a change" + tower_organization: + name: "{{ org_name }}" + register: result + +- assert: + that: + - "result is not changed" + +- name: "Try adding a bad custom_virtualenv" + tower_organization: + name: "{{ org_name }}" + custom_virtualenv: "/does/not/exit" + register: result + ignore_errors: True + +- assert: + that: + - "result is failed" + +- name: "Pass in all parameters" + tower_organization: + name: "{{ org_name }}" + description: "A description" + custom_virtualenv: "" + register: result + +- assert: + that: + - "result is changed" + +- name: "Change the description" + tower_organization: + name: "{{ org_name }}" + description: "A new description" + custom_virtualenv: "" + register: result + +- assert: + that: + - "result is changed" + +- name: "Remove the organization" + tower_organization: + name: "{{ org_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: "Remove a missing organization" + tower_organization: + name: "{{ org_name }}" + state: absent + register: result + +- assert: + that: + - "result is not changed" + +# Test behaviour common to all tower modules +- name: Check that SSL is available + tower_organization: + name: Default + register: result + +- assert: + that: result is not changed + +- name: Check that SSL is available and verify_ssl is enabled (task must fail) + tower_organization: + name: Default + environment: + TOWER_CERTIFICATE: /dev/null # force check failure + TOWER_VERIFY_SSL: True + ignore_errors: true + register: check_ssl_is_used + +- name: Check that connection failed + assert: + that: + - check_ssl_is_used is failed + +- name: Disable verify_ssl in ~/.tower_cli.cfg + copy: + dest: ~/.tower_cli.cfg + content: | + [general] + verify_ssl = False + force: false # ensure remote file doesn't exist + +- block: + - name: Check that verify_ssl is disabled (task must not fail) + tower_organization: + name: Default + environment: + TOWER_CERTIFICATE: /dev/null # should not fail because verify_ssl is disabled + always: + - name: Delete ~/.tower_cli.cfg + file: + path: ~/.tower_cli.cfg + state: absent diff --git a/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml new file mode 100644 index 0000000000..6bc3c25108 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml @@ -0,0 +1,45 @@ +- name: Fetch project_base_dir + uri: + url: "{{ lookup('env', 'TOWER_HOST') }}/api/v2/config/" + user: "{{ lookup('env', 'TOWER_USERNAME') }}" + password: "{{ lookup('env', 'TOWER_PASSWORD') }}" + validate_certs: false + return_content: true + force_basic_auth: true + register: awx_config + +- tower_inventory: + name: localhost + organization: Default + +- tower_host: + name: localhost + inventory: localhost + variables: + ansible_connection: local + +- name: create an unused SSH / Machine credential + tower_credential: + name: dummy + kind: ssh + ssh_key_data: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIUl6R1xgzR6siIUArz4XBPtGZ09aetma2eWf1v3uYymoAoGCCqGSM49 + AwEHoUQDQgAENJNjgeZDAh/+BY860s0yqrLDprXJflY0GvHIr7lX3ieCtrzOMCVU + QWzw35pc5tvuP34SSi0ZE1E+7cVMDDOF3w== + -----END EC PRIVATE KEY----- + organization: Default + +- name: Disable bubblewrap + command: tower-cli setting modify AWX_PROOT_ENABLED false + +- block: + - name: Create a directory for manual project + vars: + project_base_dir: "{{ awx_config.json.project_base_dir }}" + command: tower-cli ad_hoc launch --wait --inventory localhost + --credential dummy --module-name shell + --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }} || true" + always: + - name: enable bubblewrap + command: tower-cli setting modify AWX_PROOT_ENABLED true diff --git a/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml b/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml new file mode 100644 index 0000000000..9c22e6f7a7 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml @@ -0,0 +1,17 @@ +- name: Export all Tower assets + tower_receive: + all: True + register: result + +- assert: + that: + - "result is successful" + +- name: Extract names from output + set_fact: + object_names: "{{ result.assets | map(attribute='name') | list }}" + +- assert: + that: + - "result is successful" + - "'Default' in object_names" diff --git a/awx_collection/tests/integration/targets/tower_role/tasks/main.yml b/awx_collection/tests/integration/targets/tower_role/tasks/main.yml new file mode 100644 index 0000000000..eb1602f9b6 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_role/tasks/main.yml @@ -0,0 +1,39 @@ +- name: Create a User + tower_user: + first_name: Joe + last_name: User + username: joe + password: "{{ 65535 | random | to_uuid }}" + email: joe@example.org + state: present + register: result + +- assert: + that: + - "result is changed" + +- name: Add Joe to the update role of the default Project + tower_role: + user: joe + role: update + project: Demo Project + state: "{{ item }}" + register: result + with_items: + - "present" + - "absent" + +- assert: + that: + - "result is changed" + +- name: Delete a User + tower_user: + username: joe + email: joe@example.org + state: absent + register: result + +- assert: + that: + - "result is changed" diff --git a/awx_collection/tests/integration/targets/tower_send/tasks/main.yml b/awx_collection/tests/integration/targets/tower_send/tasks/main.yml new file mode 100644 index 0000000000..4964b283a3 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_send/tasks/main.yml @@ -0,0 +1,80 @@ +- name: Test no parameters + tower_send: + register: result + ignore_errors: yes + +- assert: + that: + - "result is failed" + +- name: Create user json + set_fact: + user: + - username: "jowestco" + first_name: "John" + last_name: "Westcott" + asset_type: "user" + email: "john.westcott.iv@redhat.com" + +- name: Test a new import of asset + tower_send: + assets: "{{ user | to_json() }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Test an existing import of asset + tower_send: + assets: "{{ user | to_json() }}" + register: result + +- assert: + that: + - "result is successful" + - "result is not changed" + +- name: Change an existing asset + tower_send: + assets: "{{ user | combine({'last_name': 'Westcott IV'}) | to_json() }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Ensure the organization is not created + tower_organization: + name: "Red Hat" + state: absent + +- name: Create organization json + set_fact: + organization: + - asset_type: organization + name: "Red Hat" + +- name: Create temp file + tempfile: + state: file + register: my_temp_file + +- name: Drop down a file to import + copy: + dest: "{{ my_temp_file.path }}" + content: "{{ organization | to_nice_json() }}" + +- name: Create org via files + tower_send: + files: "{{ my_temp_file.path }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Remove Temp File + file: + path: "{{ my_temp_file.path }}" + state: absent diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml new file mode 100644 index 0000000000..49760f6b81 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -0,0 +1,77 @@ +- name: Set the value of AWX_PROOT_SHOW_PATHS to a baseline + tower_settings: + name: AWX_PROOT_SHOW_PATHS + value: '["/var/lib/awx/projects/"]' + +- name: Set the value of AWX_PROOT_SHOW_PATHS to get an error back from Tower + tower_settings: + settings: + AWX_PROOT_SHOW_PATHS: + 'not': 'a valid' + 'tower': 'setting' + register: result + ignore_errors: True + +- assert: + that: + - "result is failed" + +- name: Set the value of AWX_PROOT_SHOW_PATHS + tower_settings: + name: AWX_PROOT_SHOW_PATHS + value: '["/var/lib/awx/projects/", "/tmp"]' + register: result + +- assert: + that: + - "result is changed" + +- name: Attempt to set the value of AWX_PROOT_BASE_PATH to what it already is + tower_settings: + name: AWX_PROOT_BASE_PATH + value: /tmp + register: result + +- debug: + msg: "{{ result }}" + +- assert: + that: + - "result is not changed" + +- name: Apply a single setting via settings + tower_settings: + name: AWX_PROOT_SHOW_PATHS + value: '["/var/lib/awx/projects/", "/var/tmp"]' + register: result + +- assert: + that: + - "result is changed" + +- name: Apply multiple setting via settings with no change + tower_settings: + name: AWX_PROOT_BASE_PATH + value: /tmp + name: AWX_PROOT_SHOW_PATHS + value: '["/var/lib/awx/projects/", "/var/tmp"]' + register: result + +- debug: + msg: "{{ result }}" + +- assert: + that: + - "result is not changed" + +- name: Apply multiple setting via settings with change + tower_settings: + name: AWX_PROOT_BASE_PATH + value: /tmp + name: AWX_PROOT_SHOW_PATHS + value: '["/var/lib/awx/new_projects/", "/tmp"]' + register: result + +- assert: + that: + - "result is changed" diff --git a/awx_collection/tests/integration/targets/tower_team/tasks/main.yml b/awx_collection/tests/integration/targets/tower_team/tasks/main.yml new file mode 100644 index 0000000000..05a48fd773 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_team/tasks/main.yml @@ -0,0 +1,48 @@ +- name: Attempt to add a Tower team to a non-existant Organization + tower_team: + name: Test Team + organization: Missing Organization + state: present + register: result + ignore_errors: yes + +- name: Assert a meaningful error was provided for the failed Tower team creation + assert: + that: + - result is failed + - "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or + result.msg =='The organizations Missing Organization was not found on the Tower server'" + +- name: Create a Tower team + tower_team: + name: Test Team + organization: Default + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Tower team + tower_team: + name: Test Team + organization: Default + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Check module fails with correct msg + tower_team: + name: Test Team + organization: Non Existing Org + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update team, organization not found: The requested object could not be found.' or + result.msg =='The organizations Non Existing Org was not found on the Tower server'" diff --git a/awx_collection/tests/integration/targets/tower_user/tasks/main.yml b/awx_collection/tests/integration/targets/tower_user/tasks/main.yml new file mode 100644 index 0000000000..0ad9f746d6 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_user/tasks/main.yml @@ -0,0 +1,94 @@ +- name: Create a User + tower_user: + first_name: Joe + last_name: User + username: joe + password: "{{ 65535 | random | to_uuid }}" + email: joe@example.org + state: present + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a User + tower_user: + username: joe + email: joe@example.org + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Create an Auditor + tower_user: + first_name: Joe + last_name: Auditor + username: joe + password: "{{ 65535 | random | to_uuid }}" + email: joe@example.org + state: present + auditor: true + register: result + +- assert: + that: + - "result is changed" + +- name: Delete an Auditor + tower_user: + username: joe + email: joe@example.org + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Create a Superuser + tower_user: + first_name: Joe + last_name: Super + username: joe + password: "{{ 65535 | random | to_uuid }}" + email: joe@example.org + state: present + superuser: true + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Superuser + tower_user: + username: joe + email: joe@example.org + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Test tower SSL parameter + tower_user: + first_name: Joe + last_name: User + username: joe + password: "{{ 65535 | random | to_uuid }}" + email: joe@example.org + state: present + validate_certs: true + tower_host: http://foo.invalid + ignore_errors: true + register: result + +- assert: + that: + - "'Unable to resolve tower_host' in result.msg or + 'Can not verify ssl with non-https protocol' in result.exception" diff --git a/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml new file mode 100644 index 0000000000..2232a8debd --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml @@ -0,0 +1,81 @@ +--- +- name: Create an SCM Credential + tower_credential: + name: SCM Credential for JT + organization: Default + kind: scm + +- name: Create a Demo Project + tower_project: + name: Job Template Test Project + organization: Default + state: present + scm_type: git + scm_url: https://github.com/ansible/ansible-tower-samples.git + scm_credential: SCM Credential for JT + register: result + +- name: Create a Job Template + tower_job_template: + name: my-job-1 + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + +- name: Create a second Job Template + tower_job_template: + name: my-job-2 + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + +- name: Add a Survey to second Job Template + tower_job_template: + name: my-job-2 + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + survey_enabled: yes + survey_spec: '{"spec": [{"index": 0, "question_name": "my question?", "default": "mydef", "variable": "myvar", "type": "text", "required": false}], "description": "test", "name": "test"}' + + +- name: Create a workflow job template + tower_workflow_template: + name: my-workflow + schema: '[{"success": [{"job_template": "my-job-1"}], "job_template": "my-job-2"}]' + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a workflow job template + tower_workflow_template: + name: my-workflow + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Check module fails with correct msg + tower_workflow_template: + name: my-workflow + organization: Non Existing Organization + schema: '[{"success": [{"job_template": "my-job-1"}], "job_template": "my-job-2"}]' + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update organization source,organization not found: The requested object could not be found.'" From fc80cf5241af122cda85eaaf1d09bb5407990efd Mon Sep 17 00:00:00 2001 From: Caleb Boylan Date: Sun, 19 Jan 2020 19:15:14 -0800 Subject: [PATCH 08/12] Replace randstr with password plugin in collection tests --- .../targets/tower_group/tasks/main.yml | 57 +++++++++++ .../targets/tower_host/tasks/main.yml | 56 +++++++++++ .../tower_inventory_source/tasks/main.yml | 34 +++++++ .../targets/tower_job_template/tasks/main.yml | 44 +++++++++ .../targets/tower_project/tasks/main.yml | 95 +++++++++++++++++++ .../tower_project_manual/tasks/main.yml | 32 +++++++ 6 files changed, 318 insertions(+) create mode 100644 awx_collection/tests/integration/targets/tower_group/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_host/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_project/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml diff --git a/awx_collection/tests/integration/targets/tower_group/tasks/main.yml b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml new file mode 100644 index 0000000000..4d163dfb6c --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml @@ -0,0 +1,57 @@ +- name: create a tempdir for hostvars + local_action: shell mktemp -d + register: tempdir + +- name: write a file w/ hostvars + local_action: + module: lineinfile + dest: "{{ tempdir.stdout }}/vars" + line: '{"foo": "bar"}' + create: true + +- name: Generate an inventory name + set_fact: + inv_name: "inv-for-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Create an Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: Default + state: present + +- name: Create a Group + tower_group: + name: Some Group + inventory: "{{ inv_name }}" + state: present + source: ec2 + variables: "@{{ tempdir.stdout }}/vars" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Group + tower_group: + name: Some Group + inventory: "{{ inv_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Check module fails with correct msg + tower_group: + name: test-group + description: Group Description + inventory: test-non-existing-inventory + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update the group, inventory not found: The requested object could not be found.'" diff --git a/awx_collection/tests/integration/targets/tower_host/tasks/main.yml b/awx_collection/tests/integration/targets/tower_host/tasks/main.yml new file mode 100644 index 0000000000..9a47bcff14 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_host/tasks/main.yml @@ -0,0 +1,56 @@ +- name: create a tempdir for hostvars + local_action: shell mktemp -d + register: tempdir + +- name: write a file w/ hostvars + local_action: + module: lineinfile + dest: "{{ tempdir.stdout }}/vars" + line: '{"foo": "bar"}' + create: true + +- name: Generate an inventory name + set_fact: + inv_name: "inv-for-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Create an Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: Default + state: present + +- name: Create a Host + tower_host: + name: "some-host" + inventory: "{{ inv_name }}" + state: present + variables: "@{{ tempdir.stdout }}/vars" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Host + tower_host: + name: "some-host" + inventory: "{{ inv_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Check module fails with correct msg + tower_host: + name: test-host + description: Host Description + inventory: test-non-existing-inventory + state: present + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update host, inventory not found: The requested object could not be found.'" diff --git a/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml new file mode 100644 index 0000000000..68658c980d --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Add a Tower credential + tower_credential: + description: Credentials for Openstack Test project + name: openstack-test-credential + kind: openstack + organization: Default + project: Test + username: admin + host: https://example.org:5000 + password: passw0rd + domain: test + +- name: Add a Tower inventory + tower_inventory: + description: Test inventory + name: openstack-test-inventory + organization: Default + +- name: Create a source inventory + tower_inventory_source: + name: "source-test-inventory {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + description: Source for Test inventory + inventory: openstack-test-inventory + credential: openstack-test-credential + overwrite: True + update_on_launch: True + source_vars: "---\nprivate: false" + source: openstack + register: result + +- assert: + that: + - "result is changed" diff --git a/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml new file mode 100644 index 0000000000..71d8594df0 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml @@ -0,0 +1,44 @@ +- name: Create an SCM Credential + tower_credential: + name: SCM Credential for JT + organization: Default + kind: scm + +- name: Create a Demo Project + tower_project: + name: Job Template Test Project + organization: Default + state: present + scm_type: git + scm_url: https://github.com/ansible/ansible-tower-samples.git + scm_credential: SCM Credential for JT + register: result + +- name: Wait for the project to be status=successful + uri: + url: "{{ lookup('ENV', 'TOWER_HOST') }}/api/v2/projects/{{ result.id }}/" + method: GET + user: "{{ lookup('ENV', 'TOWER_USERNAME') }}" + password: "{{ lookup('ENV', 'TOWER_PASSWORD') }}" + validate_certs: false + force_basic_auth: true + return_content: true + register: result + until: result.json.summary_fields.last_update is defined and result.json.summary_fields.last_update.status == "successful" + retries: 60 + delay: 1 + +- name: Create a Job Template + tower_job_template: + name: "hello-world {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + register: result + +- assert: + that: + - "result is changed" diff --git a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml new file mode 100644 index 0000000000..4e68db95eb --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml @@ -0,0 +1,95 @@ +- name: Create an SCM Credential + tower_credential: + name: SCM Credential for Project + organization: Default + kind: scm + +- name: Create a Project + tower_project: + name: my-project + organization: Default + state: present + scm_credential: SCM Credential for Project + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Project + tower_project: + name: my-project + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Create a git project without credentials + tower_project: + name: git project + organization: Default + scm_type: git + scm_url: https://github.com/ansible/ansible + +- name: "Create {{ item }}" + tower_organization: + name: "{{ item }}" + loop: + - TestOrg1 + - TestOrg2 + +- name: "Create credential" + tower_credential: + kind: scm + name: TestCred1 + organization: "{{ item }}" + loop: + - TestOrg2 + - TestOrg1 + +- name: Generate random project name appender + set_fact: + project_name_rand: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Create project TestProject + tower_project: + name: "TestProject {{ project_name_rand }}" + organization: TestOrg1 + scm_type: git + scm_url: "https://github.com/ansible/ansible" + scm_credential: TestCred1 + register: multi_org_cred_project + +- assert: + that: + - "multi_org_cred_project is changed" + +- name: Check module fails with correct msg + tower_project: + name: "TestProject {{ project_name_rand }}" + organization: Non Existing Org + scm_type: git + scm_url: "https://github.com/ansible/ansible" + scm_credential: TestCred1 + register: result + ignore_errors: true + +- assert: + that: + - "result.msg == 'Failed to update project, organization not found: Non Existing Org'" + +- name: Check module fails with correct msg + tower_project: + name: "TestProject {{ project_name_rand }}" + organization: TestOrg1 + scm_type: git + scm_url: "https://github.com/ansible/ansible" + scm_credential: Non Existing Credential + register: result + ignore_errors: true + +- assert: + that: + - "result.msg =='Failed to update project, credential not found: Non Existing Credential'" diff --git a/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml new file mode 100644 index 0000000000..44d9b5fd06 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml @@ -0,0 +1,32 @@ +- name: Generate manual project dir name + set_fact: + project_name: "manual project {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Generate manual project dir name + set_fact: + project_dir_name: "proj {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: create a project directory for manual project + import_tasks: create_project_dir.yml + +- name: Create a manual project + tower_project: + name: "{{ project_name }}" + organization: Default + scm_type: manual + local_path: "{{ project_dir_name }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a manual project + tower_project: + name: "{{ project_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" From afbeacf49983898263bc6fd4cef5da392dfb3b65 Mon Sep 17 00:00:00 2001 From: Caleb Boylan Date: Fri, 28 Feb 2020 13:49:55 -0800 Subject: [PATCH 09/12] Fix up the collection integration tests --- .../targets/tower_credential/tasks/main.yml | 5 +- .../tower_credential_type/tasks/main.yml | 2 +- .../targets/tower_group/tasks/main.yml | 19 +-- .../targets/tower_host/tasks/main.yml | 18 +-- .../targets/tower_inventory/tasks/main.yml | 5 +- .../tower_inventory_source/tasks/main.yml | 23 +++- .../targets/tower_job_cancel/tasks/main.yml | 5 +- .../targets/tower_job_launch/tasks/main.yml | 15 ++- .../targets/tower_job_list/tasks/main.yml | 3 +- .../targets/tower_job_template/tasks/main.yml | 15 +-- .../targets/tower_job_wait/tasks/main.yml | 1 + .../targets/tower_label/tasks/main.yml | 1 + .../targets/tower_notification/tasks/main.yml | 9 +- .../targets/tower_organization/tasks/main.yml | 32 ++--- .../targets/tower_project/tasks/main.yml | 120 ++++++++++-------- .../tasks/create_project_dir.yml | 23 +++- .../tower_project_manual/tasks/main.yml | 9 +- .../targets/tower_receive/tasks/main.yml | 3 +- .../targets/tower_role/tasks/main.yml | 1 + .../targets/tower_send/tasks/main.yml | 3 +- .../targets/tower_settings/tasks/main.yml | 17 ++- .../targets/tower_team/tasks/main.yml | 3 +- .../targets/tower_user/tasks/main.yml | 1 + .../tower_workflow_template/tasks/main.yml | 2 +- 24 files changed, 179 insertions(+), 156 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml index 20631bfdd0..7f4e7d3848 100644 --- a/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: create a tempdir for an SSH key local_action: shell mktemp -d register: tempdir @@ -105,7 +106,7 @@ kind: ssh username: joe ssh_key_data: "{{ ssh_key_data }}" - ignore_errors: yes + ignore_errors: true register: result - assert: @@ -120,7 +121,7 @@ state: present kind: ssh username: joe - ignore_errors: yes + ignore_errors: true register: result - assert: diff --git a/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml index c2d3edd377..9b205e0ba3 100644 --- a/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_credential_type/tasks/main.yml @@ -4,7 +4,7 @@ description: Credential type for Test name: test-credential-type kind: cloud - inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": True, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]} + inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": true, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]} injectors: {"extra_vars": {"test": "foo"}} register: result diff --git a/awx_collection/tests/integration/targets/tower_group/tasks/main.yml b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml index 4d163dfb6c..a742021bba 100644 --- a/awx_collection/tests/integration/targets/tower_group/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml @@ -1,14 +1,4 @@ -- name: create a tempdir for hostvars - local_action: shell mktemp -d - register: tempdir - -- name: write a file w/ hostvars - local_action: - module: lineinfile - dest: "{{ tempdir.stdout }}/vars" - line: '{"foo": "bar"}' - create: true - +--- - name: Generate an inventory name set_fact: inv_name: "inv-for-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" @@ -24,8 +14,8 @@ name: Some Group inventory: "{{ inv_name }}" state: present - source: ec2 - variables: "@{{ tempdir.stdout }}/vars" + variables: + foo: bar register: result - assert: @@ -54,4 +44,5 @@ - assert: that: - - "result.msg =='Failed to update the group, inventory not found: The requested object could not be found.'" + - "result.msg =='Failed to update the group, inventory not found: The requested object could not be found.' or + result.msg =='The inventories test-non-existing-inventory was not found on the Tower server'" diff --git a/awx_collection/tests/integration/targets/tower_host/tasks/main.yml b/awx_collection/tests/integration/targets/tower_host/tasks/main.yml index 9a47bcff14..90689be20c 100644 --- a/awx_collection/tests/integration/targets/tower_host/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_host/tasks/main.yml @@ -1,14 +1,4 @@ -- name: create a tempdir for hostvars - local_action: shell mktemp -d - register: tempdir - -- name: write a file w/ hostvars - local_action: - module: lineinfile - dest: "{{ tempdir.stdout }}/vars" - line: '{"foo": "bar"}' - create: true - +--- - name: Generate an inventory name set_fact: inv_name: "inv-for-group-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" @@ -24,7 +14,8 @@ name: "some-host" inventory: "{{ inv_name }}" state: present - variables: "@{{ tempdir.stdout }}/vars" + variables: + foo: bar register: result - assert: @@ -53,4 +44,5 @@ - assert: that: - - "result.msg =='Failed to update host, inventory not found: The requested object could not be found.'" + - "result.msg =='The inventories test-non-existing-inventory was not found on the Tower server' or + result.msg =='Failed to update host, inventory not found: The requested object could not be found.'" diff --git a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml index bbbed0c81d..6430afe427 100644 --- a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml @@ -1,9 +1,10 @@ +--- - name: Clean up any pre-existing test Inventory tower_inventory: name: my-inventory organization: Default state: absent - ignore_errors: True + ignore_errors: true - name: Create an Inventory @@ -34,7 +35,7 @@ organization: Default kind: smart register: result - ignore_errors: True + ignore_errors: true - assert: that: diff --git a/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml index 68658c980d..5cd545bf6c 100644 --- a/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_inventory_source/tasks/main.yml @@ -14,8 +14,8 @@ - name: Add a Tower inventory tower_inventory: description: Test inventory - name: openstack-test-inventory organization: Default + name: openstack-test-inventory - name: Create a source inventory tower_inventory_source: @@ -23,12 +23,27 @@ description: Source for Test inventory inventory: openstack-test-inventory credential: openstack-test-credential - overwrite: True - update_on_launch: True - source_vars: "---\nprivate: false" + overwrite: true + update_on_launch: true + source_vars: + private: false source: openstack register: result - assert: that: - "result is changed" + +- name: Delete the source inventory + tower_inventory_source: + name: "{{ result.name }}" + description: Source for Test inventory + inventory: openstack-test-inventory + credential: openstack-test-credential + overwrite: true + update_on_launch: true + source_vars: + private: false + source: openstack + state: absent + ignore_errors: true diff --git a/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml index 4f7550f276..d949610d98 100644 --- a/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_cancel/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Launch a Job Template tower_job_launch: job_template: "Demo Job Template" @@ -19,9 +20,9 @@ - name: Cancel an already canceled job (assert failure) tower_job_cancel: job_id: "{{ job.id }}" - fail_if_not_running: True + fail_if_not_running: true register: results - ignore_errors: True + ignore_errors: true - assert: that: diff --git a/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml index 7436710e91..cd64fee9d0 100644 --- a/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_launch/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Launch a Job Template tower_job_launch: job_template: "Demo Job Template" @@ -22,9 +23,9 @@ - name: Check module fails with correct msg tower_job_launch: - job_template: "Non Existing Job Template" - inventory: "Test Inventory" - credential: "Test Credential" + job_template: "Non Existing Job Template" + inventory: "Test Inventory" + credential: "Test Credential" register: result ignore_errors: true @@ -39,8 +40,8 @@ project: Demo Project playbook: hello_world.yml job_type: run - ask_credential: yes - ask_inventory: yes + ask_credential: true + ask_inventory: true state: present register: result @@ -86,7 +87,7 @@ - name: Get the job tower_job_list: - query: {"id": "{{result.id}}" } + query: {"id": "{{result.id}}"} register: result - assert: @@ -98,4 +99,4 @@ name: "Demo Job Template - extra_vars" organization: Default state: absent - ignore_errors: True + ignore_errors: true diff --git a/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml index 47317c0919..a883f28d87 100644 --- a/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_list/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Launch a Job Template tower_job_launch: job_template: "Demo Job Template" @@ -29,7 +30,7 @@ - name: Get ALL result pages! tower_job_list: - all_pages: True + all_pages: true register: all_page_query - assert: diff --git a/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml index 71d8594df0..ccd295c0d5 100644 --- a/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Create an SCM Credential tower_credential: name: SCM Credential for JT @@ -14,20 +15,6 @@ scm_credential: SCM Credential for JT register: result -- name: Wait for the project to be status=successful - uri: - url: "{{ lookup('ENV', 'TOWER_HOST') }}/api/v2/projects/{{ result.id }}/" - method: GET - user: "{{ lookup('ENV', 'TOWER_USERNAME') }}" - password: "{{ lookup('ENV', 'TOWER_PASSWORD') }}" - validate_certs: false - force_basic_auth: true - return_content: true - register: result - until: result.json.summary_fields.last_update is defined and result.json.summary_fields.last_update.status == "successful" - retries: 60 - delay: 1 - - name: Create a Job Template tower_job_template: name: "hello-world {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" diff --git a/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml index 0b46f00f7d..589ea8629f 100644 --- a/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_wait/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Launch a Job Template tower_job_launch: job_template: "Demo Job Template" diff --git a/awx_collection/tests/integration/targets/tower_label/tasks/main.yml b/awx_collection/tests/integration/targets/tower_label/tasks/main.yml index f86712b96a..76b65c62e7 100644 --- a/awx_collection/tests/integration/targets/tower_label/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_label/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Create a Label tower_label: name: important diff --git a/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml b/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml index 4fff1ae6f9..277fee7a81 100644 --- a/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_notification/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Create Slack notification tower_notification: name: notification1 @@ -64,8 +65,8 @@ - user1@example.com host: smtp.example.com port: 25 - use_tls: no - use_ssl: no + use_tls: false + use_ssl: false state: present register: result @@ -153,7 +154,7 @@ color: red rooms: - room-A - notify: yes + notify: true state: present register: result @@ -184,7 +185,7 @@ - user1 port: 8080 server: irc.example.com - use_ssl: no + use_ssl: false state: present register: result diff --git a/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml index c5577ce9e3..b7e16935ef 100644 --- a/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_organization/tasks/main.yml @@ -1,6 +1,7 @@ +--- - name: Generate an org name set_fact: - org_name: "org-{{ lookup('randstr') }}" + org_name: "org-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - name: Make sure {{ org_name }} is not there tower_organization: @@ -30,7 +31,7 @@ name: "{{ org_name }}" custom_virtualenv: "/does/not/exit" register: result - ignore_errors: True + ignore_errors: true - assert: that: @@ -90,9 +91,9 @@ - name: Check that SSL is available and verify_ssl is enabled (task must fail) tower_organization: name: Default + validate_certs: true environment: TOWER_CERTIFICATE: /dev/null # force check failure - TOWER_VERIFY_SSL: True ignore_errors: true register: check_ssl_is_used @@ -101,22 +102,9 @@ that: - check_ssl_is_used is failed -- name: Disable verify_ssl in ~/.tower_cli.cfg - copy: - dest: ~/.tower_cli.cfg - content: | - [general] - verify_ssl = False - force: false # ensure remote file doesn't exist - -- block: - - name: Check that verify_ssl is disabled (task must not fail) - tower_organization: - name: Default - environment: - TOWER_CERTIFICATE: /dev/null # should not fail because verify_ssl is disabled - always: - - name: Delete ~/.tower_cli.cfg - file: - path: ~/.tower_cli.cfg - state: absent +- name: Check that verify_ssl is disabled (task must not fail) + tower_organization: + name: Default + validate_certs: false + environment: + TOWER_CERTIFICATE: /dev/null # should not fail because verify_ssl is disabled diff --git a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml index 4e68db95eb..c6d5d3230f 100644 --- a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml @@ -1,95 +1,115 @@ +--- +- name: Delete old git project from any previous test runs + tower_project: + name: "git project" + organization: Default + state: absent + ignore_errors: true + - name: Create an SCM Credential tower_credential: name: SCM Credential for Project organization: Default kind: scm -- name: Create a Project +- name: Create a git project without credentials without waiting tower_project: - name: my-project - organization: Default - state: present - scm_credential: SCM Credential for Project - register: result - -- assert: - that: - - "result is changed" - -- name: Delete a Project - tower_project: - name: my-project - state: absent - register: result - -- assert: - that: - - "result is changed" - -- name: Create a git project without credentials - tower_project: - name: git project + name: "git project" organization: Default scm_type: git - scm_url: https://github.com/ansible/ansible + scm_url: https://github.com/ansible/test-playbooks + wait: false + register: result -- name: "Create {{ item }}" +- assert: + that: + - result is changed + +- name: Recreate the project to validate not changed + tower_project: + name: "git project" + organization: Default + scm_type: git + scm_url: https://github.com/ansible/test-playbooks + wait: true + register: result + +- assert: + that: + - result is not changed + +- name: Create organizations tower_organization: - name: "{{ item }}" - loop: - - TestOrg1 - - TestOrg2 + name: TestOrg -- name: "Create credential" +- name: Create credential tower_credential: kind: scm - name: TestCred1 - organization: "{{ item }}" - loop: - - TestOrg2 - - TestOrg1 + name: TestCred + organization: TestOrg + + register: new_credentials - name: Generate random project name appender set_fact: project_name_rand: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" -- name: Create project TestProject +- name: Create a new test project in check_mode tower_project: name: "TestProject {{ project_name_rand }}" - organization: TestOrg1 + organization: TestOrg scm_type: git - scm_url: "https://github.com/ansible/ansible" - scm_credential: TestCred1 - register: multi_org_cred_project + scm_url: https://github.com/ansible/test-playbooks + scm_credential: TestCred + check_mode: true +- name: Create a new test project + tower_project: + name: "TestProject {{ project_name_rand }}" + organization: TestOrg + scm_type: git + scm_url: https://github.com/ansible/test-playbooks + scm_credential: TestCred + register: result + +# If this fails it may be because the check_mode task actually already created +# the project, or it could be because the module actually failed somehow - assert: that: - - "multi_org_cred_project is changed" + - "result is changed" -- name: Check module fails with correct msg +- name: Check module fails with correct msg when given non-existing org as param tower_project: name: "TestProject {{ project_name_rand }}" organization: Non Existing Org scm_type: git - scm_url: "https://github.com/ansible/ansible" - scm_credential: TestCred1 + scm_url: https://github.com/ansible/test-playbooks + scm_credential: TestCred register: result ignore_errors: true - assert: that: - - "result.msg == 'Failed to update project, organization not found: Non Existing Org'" + - "result.msg == 'The organizations Non Existing Org was not found on the Tower server' or + result.msg == 'Failed to update project, organization not found: Non Existing Org'" -- name: Check module fails with correct msg +- name: Check module fails with correct msg when given non-existing credential as param tower_project: name: "TestProject {{ project_name_rand }}" - organization: TestOrg1 + organization: TestOrg scm_type: git - scm_url: "https://github.com/ansible/ansible" + scm_url: https://github.com/ansible/test-playbooks scm_credential: Non Existing Credential register: result ignore_errors: true - assert: that: - - "result.msg =='Failed to update project, credential not found: Non Existing Credential'" + - "result.msg =='The credentials Non Existing Credential was not found on the Tower server' or + result.msg =='Failed to update project, credential not found: Non Existing Credential'" + +- name: Delete the test project + tower_project: + name: "TestProject {{ project_name_rand }}" + organization: TestOrg + state: absent diff --git a/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml index 6bc3c25108..7f5b3b49c2 100644 --- a/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml +++ b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml @@ -1,8 +1,21 @@ +--- +- name: get tower host variable + shell: tower-cli config host | cut -d ' ' -f2 + register: host + +- name: get tower username variable + shell: tower-cli config username | cut -d ' ' -f2 + register: username + +- name: get tower password variable + shell: tower-cli config password | cut -d ' ' -f2 + register: password + - name: Fetch project_base_dir uri: - url: "{{ lookup('env', 'TOWER_HOST') }}/api/v2/config/" - user: "{{ lookup('env', 'TOWER_USERNAME') }}" - password: "{{ lookup('env', 'TOWER_PASSWORD') }}" + url: "{{ host.stdout }}/api/v2/config/" + user: "{{ username.stdout }}" + password: "{{ password.stdout }}" validate_certs: false return_content: true force_basic_auth: true @@ -38,8 +51,8 @@ vars: project_base_dir: "{{ awx_config.json.project_base_dir }}" command: tower-cli ad_hoc launch --wait --inventory localhost - --credential dummy --module-name shell - --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }} || true" + --credential dummy --module-name command + --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }}" always: - name: enable bubblewrap command: tower-cli setting modify AWX_PROOT_ENABLED true diff --git a/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml index 44d9b5fd06..8718219efb 100644 --- a/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project_manual/tasks/main.yml @@ -1,10 +1,14 @@ +--- +- name: generate random string for project + set_fact: + rand_string: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - name: Generate manual project dir name set_fact: - project_name: "manual project {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + project_name: "manual project {{ rand_string }}" - name: Generate manual project dir name set_fact: - project_dir_name: "proj {{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + project_dir_name: "proj_{{ rand_string }}" - name: create a project directory for manual project import_tasks: create_project_dir.yml @@ -24,6 +28,7 @@ - name: Delete a manual project tower_project: name: "{{ project_name }}" + organization: Default state: absent register: result diff --git a/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml b/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml index 9c22e6f7a7..e592a2bb3a 100644 --- a/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_receive/tasks/main.yml @@ -1,6 +1,7 @@ +--- - name: Export all Tower assets tower_receive: - all: True + all: true register: result - assert: diff --git a/awx_collection/tests/integration/targets/tower_role/tasks/main.yml b/awx_collection/tests/integration/targets/tower_role/tasks/main.yml index eb1602f9b6..f35e3d5846 100644 --- a/awx_collection/tests/integration/targets/tower_role/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_role/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Create a User tower_user: first_name: Joe diff --git a/awx_collection/tests/integration/targets/tower_send/tasks/main.yml b/awx_collection/tests/integration/targets/tower_send/tasks/main.yml index 4964b283a3..ac5ba84bbc 100644 --- a/awx_collection/tests/integration/targets/tower_send/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_send/tasks/main.yml @@ -1,7 +1,8 @@ +--- - name: Test no parameters tower_send: register: result - ignore_errors: yes + ignore_errors: true - assert: that: diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml index 49760f6b81..a02ca673de 100644 --- a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Set the value of AWX_PROOT_SHOW_PATHS to a baseline tower_settings: name: AWX_PROOT_SHOW_PATHS @@ -10,7 +11,7 @@ 'not': 'a valid' 'tower': 'setting' register: result - ignore_errors: True + ignore_errors: true - assert: that: @@ -51,10 +52,9 @@ - name: Apply multiple setting via settings with no change tower_settings: - name: AWX_PROOT_BASE_PATH - value: /tmp - name: AWX_PROOT_SHOW_PATHS - value: '["/var/lib/awx/projects/", "/var/tmp"]' + settings: + AWX_PROOT_BASE_PATH: /tmp + AWX_PROOT_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"] register: result - debug: @@ -66,10 +66,9 @@ - name: Apply multiple setting via settings with change tower_settings: - name: AWX_PROOT_BASE_PATH - value: /tmp - name: AWX_PROOT_SHOW_PATHS - value: '["/var/lib/awx/new_projects/", "/tmp"]' + settings: + AWX_PROOT_BASE_PATH: /tmp + AWX_PROOT_SHOW_PATHS: [] register: result - assert: diff --git a/awx_collection/tests/integration/targets/tower_team/tasks/main.yml b/awx_collection/tests/integration/targets/tower_team/tasks/main.yml index 05a48fd773..a7cd6ad16d 100644 --- a/awx_collection/tests/integration/targets/tower_team/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_team/tasks/main.yml @@ -1,10 +1,11 @@ +--- - name: Attempt to add a Tower team to a non-existant Organization tower_team: name: Test Team organization: Missing Organization state: present register: result - ignore_errors: yes + ignore_errors: true - name: Assert a meaningful error was provided for the failed Tower team creation assert: diff --git a/awx_collection/tests/integration/targets/tower_user/tasks/main.yml b/awx_collection/tests/integration/targets/tower_user/tasks/main.yml index 0ad9f746d6..6360733e8f 100644 --- a/awx_collection/tests/integration/targets/tower_user/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_user/tasks/main.yml @@ -1,3 +1,4 @@ +--- - name: Create a User tower_user: first_name: Joe diff --git a/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml index 2232a8debd..201c452890 100644 --- a/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_workflow_template/tasks/main.yml @@ -44,7 +44,7 @@ credential: Demo Credential job_type: run state: present - survey_enabled: yes + survey_enabled: true survey_spec: '{"spec": [{"index": 0, "question_name": "my question?", "default": "mydef", "variable": "myvar", "type": "text", "required": false}], "description": "test", "name": "test"}' From d14bf00f6c69bdc8102c8dca5dd16964ec79863c Mon Sep 17 00:00:00 2001 From: Caleb Boylan Date: Fri, 28 Feb 2020 19:48:18 -0800 Subject: [PATCH 10/12] Add collection integration test instructions to the README --- awx_collection/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/awx_collection/README.md b/awx_collection/README.md index 7e1022892e..f91396e59c 100644 --- a/awx_collection/README.md +++ b/awx_collection/README.md @@ -37,7 +37,7 @@ To use this collection in AWX, you should create a custom virtual environment in to set the job template `extra_vars` to include `ansible_python_interpreter` to be the Python in that virtual environment. -## Running Tests +## Running Unit Tests Tests to verify compatibility with the most recent AWX code are in `awx_collection/test/awx`. These tests require that Python packages @@ -71,6 +71,30 @@ pip install -e . PYTHONPATH=awx_collection:$PYTHONPATH py.test awx_collection/test/awx/ ``` +## Running Integration tests Tests + +The integration tests require a virtualenv with `ansible` >= 2.9 and `tower_cli`. +The collection must first be installed, which can be done using `make install_collection`. +You also need a configuration file at `~/.tower_cli.cfg` or +`/etc/tower/tower_cli.cfg` with the credentials for accessing tower. This can +be populated using `tower-cli`: + +``` +tower-cli config host $HOST +tower-cli config username $USERNAME +tower-cli config password $PASSWORD +# This tells the tower-cli not to veriffy the ssl certs in the tower, if your tower has good certs you should leave this to true +tower-cli config verify_ssl false +``` + +Finally you can run the tests: + +``` +# ansible-test must be run from the directory in which the collection is installed +cd ~/.ansible/collections/ansible_collections/awx/awx/ +ansible-test integration +``` + ## Building The build target `make build_collection` will template out a `galaxy.yml` file From 32ef805e23b4ee0b561c41091d3e619fae5656b5 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 3 Mar 2020 14:28:46 -0500 Subject: [PATCH 11/12] properly support job host summary data in custom notification templates see: https://github.com/ansible/tower/issues/4148 --- awx/main/models/notifications.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index 71a68b2a9f..7a20ed4e19 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -270,7 +270,8 @@ class JobNotificationMixin(object): 'elapsed', 'job_explanation', 'execution_node', 'controller_node', 'allow_simultaneous', 'scm_revision', 'diff_mode', 'job_slice_number', 'job_slice_count', 'custom_virtualenv', 'approval_status', 'approval_node_name', 'workflow_url', - {'host_status_counts': ['skipped', 'ok', 'changed', 'failures', 'dark']}, + {'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark' + 'processed', 'rescued', 'ignored']}, {'playbook_counts': ['play_count', 'task_count']}, {'summary_fields': [{'inventory': ['id', 'name', 'description', 'has_active_failures', 'total_hosts', 'hosts_with_active_failures', 'total_groups', @@ -303,7 +304,7 @@ class JobNotificationMixin(object): 'finished': False, 'force_handlers': False, 'forks': 0, - 'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0}, + 'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0, 'failed': False, 'processed': 0, 'rescued': 0}, 'id': 42, 'job_explanation': 'Sample job explanation', 'job_slice_count': 1, @@ -392,10 +393,20 @@ class JobNotificationMixin(object): The context will contain whitelisted content retrieved from a serialized job object (see JobNotificationMixin.JOB_FIELDS_WHITELIST), the job's friendly name, and a url to the job run.""" - context = {'job': {}, - 'job_friendly_name': self.get_notification_friendly_name(), - 'url': self.get_ui_url(), - 'job_metadata': json.dumps(self.notification_data(), indent=4)} + job_context = {'host_status_counts': {}} + summary = None + if hasattr(self, 'job_host_summaries'): + summary = self.job_host_summaries.first() + if summary: + from awx.api.serializers import JobHostSummarySerializer + summary_data = JobHostSummarySerializer(summary).to_representation(summary) + job_context['host_status_counts'] = summary_data + context = { + 'job': job_context, + 'job_friendly_name': self.get_notification_friendly_name(), + 'url': self.get_ui_url(), + 'job_metadata': json.dumps(self.notification_data(), indent=4) + } def build_context(node, fields, whitelisted_fields): for safe_field in whitelisted_fields: From e7f36eb2ead58a0e75f2e682463e5e09d0f8ccf0 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 3 Mar 2020 14:58:36 -0500 Subject: [PATCH 12/12] remove a few custom notification fields that don't work these aren't top-level serializer fields; they're summary fields if we want to support these properly, we should treat them as enhancements, and write support, tests, and documentation --- awx/main/models/notifications.py | 16 ++-------------- .../functional/models/test_notifications.py | 17 ++++++----------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index 7a20ed4e19..f731a03ce0 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -272,20 +272,17 @@ class JobNotificationMixin(object): 'approval_status', 'approval_node_name', 'workflow_url', {'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark' 'processed', 'rescued', 'ignored']}, - {'playbook_counts': ['play_count', 'task_count']}, {'summary_fields': [{'inventory': ['id', 'name', 'description', 'has_active_failures', 'total_hosts', 'hosts_with_active_failures', 'total_groups', 'has_inventory_sources', 'total_inventory_sources', 'inventory_sources_with_failures', 'organization_id', 'kind']}, {'project': ['id', 'name', 'description', 'status', 'scm_type']}, - {'project_update': ['id', 'name', 'description', 'status', 'failed']}, {'job_template': ['id', 'name', 'description']}, {'unified_job_template': ['id', 'name', 'description', 'unified_job_type']}, {'instance_group': ['name', 'id']}, {'created_by': ['id', 'username', 'first_name', 'last_name']}, - {'labels': ['count', 'results']}, - {'source_workflow_job': ['description', 'elapsed', 'failed', 'id', 'name', 'status']}]}] + {'labels': ['count', 'results']}]}] @classmethod def context_stub(cls): @@ -315,7 +312,6 @@ class JobNotificationMixin(object): 'limit': 'bar_limit', 'modified': datetime.datetime(2018, 12, 13, 6, 4, 0, 0, tzinfo=datetime.timezone.utc), 'name': 'Stub JobTemplate', - 'playbook_counts': {'play_count': 5, 'task_count': 10}, 'playbook': 'ping.yml', 'scm_revision': '', 'skip_tags': '', @@ -348,18 +344,10 @@ class JobNotificationMixin(object): 'name': 'Stub project', 'scm_type': 'git', 'status': 'successful'}, - 'project_update': {'id': 5, 'name': 'Stub Project Update', 'description': 'Project Update', - 'status': 'running', 'failed': False}, 'unified_job_template': {'description': 'Sample unified job template description', 'id': 39, 'name': 'Stub Job Template', - 'unified_job_type': 'job'}, - 'source_workflow_job': {'description': 'Sample workflow job description', - 'elapsed': 0.000, - 'failed': False, - 'id': 88, - 'name': 'Stub WorkflowJobTemplate', - 'status': 'running'}}, + 'unified_job_type': 'job'}}, 'timeout': 0, 'type': 'job', 'url': '/api/v2/jobs/13/', diff --git a/awx/main/tests/functional/models/test_notifications.py b/awx/main/tests/functional/models/test_notifications.py index 02a2b28e83..57fd4cca91 100644 --- a/awx/main/tests/functional/models/test_notifications.py +++ b/awx/main/tests/functional/models/test_notifications.py @@ -23,8 +23,11 @@ class TestJobNotificationMixin(object): 'finished': bool, 'force_handlers': bool, 'forks': int, - 'host_status_counts': {'skipped': int, 'ok': int, 'changed': int, - 'failures': int, 'dark': int}, + 'host_status_counts': { + 'skipped': int, 'ok': int, 'changed': int, + 'failures': int, 'dark': int, 'processed': int, + 'rescued': int, 'failed': bool + }, 'id': int, 'job_explanation': str, 'job_slice_count': int, @@ -36,7 +39,6 @@ class TestJobNotificationMixin(object): 'modified': datetime.datetime, 'name': str, 'playbook': str, - 'playbook_counts': {'play_count': int, 'task_count': int}, 'scm_revision': str, 'skip_tags': str, 'start_at_task': str, @@ -68,17 +70,10 @@ class TestJobNotificationMixin(object): 'name': str, 'scm_type': str, 'status': str}, - 'project_update': {'id': int, 'name': str, 'description': str, 'status': str, 'failed': bool}, 'unified_job_template': {'description': str, 'id': int, 'name': str, - 'unified_job_type': str}, - 'source_workflow_job': {'description': str, - 'elapsed': float, - 'failed': bool, - 'id': int, - 'name': str, - 'status': str}}, + 'unified_job_type': str}}, 'timeout': int, 'type': str,