├── .gitignore
├── Adobe
├── CFBundleVersionSmartGroupTemplate.xml
├── CreativeCloudApp.jss.recipe
├── CreativeCloudApp.munki.recipe
├── CreativeCloudApp.pkg.recipe
├── CreativeCloudAppNoUninstall.munki.recipe
├── CreativeCloudAppSerialized.munki.recipe
├── CreativeCloudAppSerialized.pkg.recipe
├── CreativeCloudBuildModifier.py
├── CreativeCloudFeed.py
├── CreativeCloudPackager.py
├── CreativeCloudVersioner.py
├── PolicyTemplate.xml
├── SmartGroupTemplate.xml
├── UninstallPolicyTemplate.xml
├── UninstallSmartGroupTemplate.xml
└── examples
│ ├── AcrobatDC17.jss.recipe
│ ├── AcrobatDC17.munki.recipe
│ ├── AcrobatDCSmartGroupTemplate.xml
│ ├── AdobeAuditionCC2017.jss.recipe
│ ├── AdobePhotoshopCC2017.munki.recipe
│ ├── AdobePhotoshopCC2017.pkg.recipe
│ ├── PolicyTemplate.xml
│ ├── README.md
│ └── SmartGroupTemplate.xml
├── LICENSE
├── README.md
├── list_ccp_feed
└── whats_my_org.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
--------------------------------------------------------------------------------
/Adobe/CFBundleVersionSmartGroupTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 | %group_name%
3 | true
4 |
5 |
6 | Application Title
7 | 0
8 | and
9 | is
10 | %JSS_INVENTORY_NAME%
11 |
12 |
13 | %NAME%Version
14 | 1
15 | and
16 | is not
17 | %VERSION%
18 |
19 |
20 | %NAME%Version
21 | 2
22 | and
23 | is not
24 |
25 |
26 |
27 | Computer Group
28 | 3
29 | and
30 | member of
31 | Testing
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudApp.jss.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Build and import a Creative Cloud Application into JSS
7 | Identifier
8 | com.github.mosen.jss.Adobe.CreativeCloudApp
9 | Input
10 |
11 | CATEGORY
12 | Productivity
13 | GROUP_NAME
14 | %NAME%-update-smart
15 | GROUP_TEMPLATE
16 | SmartGroupTemplate.xml
17 | POLICY_CATEGORY
18 | Testing
19 | POLICY_TEMPLATE
20 | PolicyTemplate.xml
21 | UNINSTALL_CATEGORY
22 | Uninstallers
23 | UNINSTALL_GROUP_NAME
24 | %NAME%-uninstall
25 | UNINSTALL_GROUP_TEMPLATE
26 | UninstallSmartGroupTemplate.xml
27 | UNINSTALL_POLICY_CATEGORY
28 | Testing
29 | UNINSTALL_POLICY_TEMPLATE
30 | UninstallPolicyTemplate.xml
31 | SELF_SERVICE_ICON
32 |
33 |
34 | MinimumVersion
35 | 1.0.0
36 | ParentRecipe
37 | com.github.mosen.pkg.Adobe.CreativeCloudApp
38 | Process
39 |
40 |
41 | Processor
42 | Copier
43 | Arguments
44 |
45 | source_path
46 | %pkg_path%
47 | destination_path
48 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.pkg
49 |
50 |
51 |
52 | Arguments
53 |
54 | category
55 | %CATEGORY%
56 | groups
57 |
58 |
59 | name
60 | %GROUP_NAME%
61 | smart
62 |
63 | template_path
64 | %GROUP_TEMPLATE%
65 |
66 |
67 | jss_inventory_name
68 | %jss_inventory_name%
69 | pkg_path
70 | %destination_path%
71 | policy_category
72 | %POLICY_CATEGORY%
73 | policy_template
74 | %POLICY_TEMPLATE%
75 | prod_name
76 | %NAME%
77 | self_service_description
78 | %release_notes%
79 | self_service_icon
80 | %icon_path%
81 |
82 | Processor
83 | JSSImporter
84 |
85 |
86 | Processor
87 | PathDeleter
88 | Arguments
89 |
90 | path_list
91 | %destination_path%
92 |
93 |
94 |
95 | Processor
96 | Copier
97 | Arguments
98 |
99 | source_path
100 | %uninstaller_pkg_path%
101 | destination_path
102 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%version%.pkg
103 |
104 |
105 |
106 | Arguments
107 |
108 | category
109 | %UNINSTALL_CATEGORY%
110 | groups
111 |
112 |
113 | name
114 | %UNINSTALL_GROUP_NAME%
115 | smart
116 |
117 | template_path
118 | %UNINSTALL_GROUP_TEMPLATE%
119 |
120 |
121 | jss_inventory_name
122 | %jss_inventory_name%
123 | pkg_path
124 | %destination_path%
125 | policy_category
126 | %UNINSTALL_POLICY_CATEGORY%
127 | policy_template
128 | %UNINSTALL_POLICY_TEMPLATE%
129 | prod_name
130 | %NAME%
131 | self_service_icon
132 | %icon_path%
133 |
134 | Processor
135 | JSSImporter
136 |
137 |
138 | Processor
139 | PathDeleter
140 | Arguments
141 |
142 | path_list
143 | %destination_path%
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudApp.munki.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Build and import a Creative Cloud Application into munki
7 | Identifier
8 | com.github.mosen.munki.Adobe.CreativeCloudApp
9 | Input
10 |
11 | MUNKI_REPO_SUBDIR
12 | apps/AdobeCC
13 |
14 | FETCH_ICON
15 | false
16 |
17 | FETCH_RELEASE_NOTES
18 | false
19 |
25 | pkginfo
26 |
27 | catalogs
28 |
29 | testing
30 |
31 | developer
32 | Adobe
33 | name
34 | %NAME%
35 |
36 |
37 | MinimumVersion
38 | 1.0.0
39 | ParentRecipe
40 | com.github.mosen.pkg.Adobe.CreativeCloudApp
41 | Process
42 |
43 |
46 |
47 | Processor
48 | DmgCreator
49 | Arguments
50 |
51 | dmg_root
52 | %pkg_path%
53 | dmg_path
54 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg
55 |
56 |
57 |
58 | Processor
59 | DmgCreator
60 | Arguments
61 |
62 | dmg_root
63 | %uninstaller_pkg_path%
64 | dmg_path
65 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg
66 |
67 |
68 |
69 |
70 | Processor
71 | MunkiPkginfoMerger
72 |
73 |
74 |
75 | Processor
76 | MunkiPkginfoMerger
77 | Arguments
78 |
79 | additional_pkginfo
80 |
81 | version
82 | %user_facing_version%
83 | description
84 | %release_notes%
85 |
86 |
87 |
88 |
89 | Arguments
90 |
91 | pkg_path
92 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg
93 | uninstaller_pkg_path
94 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg
95 | repo_subdirectory
96 | %MUNKI_REPO_SUBDIR%
97 |
98 | Processor
99 | MunkiImporter
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudApp.pkg.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Use Creative Cloud Packager to configure and build a package for a creative cloud
7 | product which uses the 'HyperDrive' installer engine. This recipe must be overridden
8 | and have at least NAME, PRODUCT_ID, BASE_VERSION and ORG_NAME defined for a given
9 | product.
10 |
11 | A 'product' is a unique combination of a PRODUCT_ID and a BASE_VERSION. For example,
12 | Photoshop CC 2017 would be PRODUCT_ID 'PHSP' and BASE_VERSION '18.0'.
13 |
14 | Internally Adobe calls these PRODUCT_IDs "SAP Codes", and these can be found here:
15 | https://helpx.adobe.com/creative-cloud/packager/apps-deployed-without-their-base-versions.html
16 |
17 | There a script in this recipe's directory, 'listfeed.py', which when run will query
18 | the product feed and display all known versions, their SAP codes and base versions.
19 | Only products with base versions defined can be used with this recipe.
20 |
21 | VERSION defaults to 'latest', which will retrieve the latest version for that product.
22 | A previous version can be specified.
23 |
24 | Other Input values here correspond to options provided in the CCP UI.
25 | Identifier
26 | com.github.mosen.pkg.Adobe.CreativeCloudApp
27 | Input
28 |
29 | NAME
30 | CreativeCloudApp
31 | FETCH_ICON
32 | false
33 | FETCH_RELEASE_NOTES
34 | false
35 | ccpinfo
36 |
37 |
38 |
39 |
40 | matchOSLanguage
41 |
42 |
43 |
44 | rumEnabled
45 |
46 |
47 |
48 | updatesEnabled
49 |
50 |
51 |
52 | appsPanelEnabled
53 |
54 |
55 |
56 | adminPrivilegesEnabled
57 |
58 |
59 |
60 | organizationName
61 | ADMIN_PLEASE_CHANGE
62 |
63 |
64 | customerType
65 | team
66 | Language
67 | en_US
68 |
69 |
70 | Products
71 |
72 |
73 | sapCode
74 |
75 | baseVersion
76 |
77 | version
78 | latest
79 |
80 |
81 |
82 |
83 | MinimumVersion
84 | 0.4.0
85 | Process
86 |
87 |
88 | Processor
89 | CreativeCloudFeed
90 | Arguments
91 |
92 | fetch_icon
93 | %FETCH_ICON%
94 | fetch_release_notes
95 | %FETCH_RELEASE_NOTES%
96 |
97 |
98 |
99 | Processor
100 | CreativeCloudPackager
101 | Arguments
102 |
103 | package_name
104 | %NAME%
105 |
106 |
107 |
108 | Processor
109 | EndOfCheckPhase
110 |
111 |
112 | Processor
113 | CreativeCloudVersioner
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudAppNoUninstall.munki.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Build and import a Creative Cloud Application into munki (for applications that do not have an uninstaller)
7 | Identifier
8 | com.github.mosen.munki.Adobe.CreativeCloudAppNoUninstall
9 | Input
10 |
11 | MUNKI_REPO_SUBDIR
12 | apps/AdobeCC
13 | pkginfo
14 |
15 |
16 |
17 |
18 | MinimumVersion
19 | 1.0.0
20 | ParentRecipe
21 | com.github.mosen.pkg.Adobe.CreativeCloudApp
22 | Process
23 |
24 |
27 |
28 | Processor
29 | DmgCreator
30 | Arguments
31 |
32 | dmg_root
33 | %RECIPE_CACHE_DIR%/%NAME%
34 | dmg_path
35 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.dmg
36 |
37 |
38 |
39 | Arguments
40 |
41 | additional_pkginfo
42 |
43 | version
44 | %version%
45 | minimum_os_version
46 | %minimum_os_version%
47 | display_name
48 | %display_name%
49 |
50 |
51 | Processor
52 | MunkiPkginfoMerger
53 |
54 |
55 | Arguments
56 |
57 | pkg_path
58 | %RECIPE_CACHE_DIR%/%NAME%_Install-%version%.dmg
59 | uninstaller_pkg_path
60 |
61 | repo_subdirectory
62 | %MUNKI_REPO_SUBDIR%
63 |
64 | Processor
65 | MunkiImporter
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudAppSerialized.munki.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Build and import a Serialized Creative Cloud Application (without CCDA) into munki
7 | Identifier
8 | com.github.mosen.munki.Adobe.CreativeCloudAppSerialized
9 | Input
10 |
11 | MUNKI_REPO_SUBDIR
12 | apps/AdobeCC
13 |
19 | pkginfo
20 |
21 | catalogs
22 |
23 | testing
24 |
25 | developer
26 | Adobe
27 | name
28 | %NAME%
29 |
30 |
31 | MinimumVersion
32 | 1.0.0
33 | ParentRecipe
34 | com.github.mosen.pkg.Adobe.CreativeCloudApp
35 | Process
36 |
37 |
40 |
41 | Processor
42 | CreativeCloudBuildModifier
43 | Arguments
44 |
45 | suppress_ccda
46 |
47 |
48 |
49 |
50 | Processor
51 | DmgCreator
52 | Arguments
53 |
54 | dmg_root
55 | %pkg_path%
56 | dmg_path
57 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg
58 |
59 |
60 |
61 | Processor
62 | DmgCreator
63 | Arguments
64 |
65 | dmg_root
66 | %uninstaller_pkg_path%
67 | dmg_path
68 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg
69 |
70 |
71 |
72 |
73 | Processor
74 | MunkiPkginfoMerger
75 |
76 |
77 |
78 | Processor
79 | MunkiPkginfoMerger
80 | Arguments
81 |
82 | additional_pkginfo
83 |
84 | version
85 | %user_facing_version%
86 |
87 |
88 |
89 |
90 | Arguments
91 |
92 | pkg_path
93 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg
94 | uninstaller_pkg_path
95 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg
96 | repo_subdirectory
97 | %MUNKI_REPO_SUBDIR%
98 |
99 | Processor
100 | MunkiImporter
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudAppSerialized.pkg.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | Build and import a Serialized Creative Cloud Application (without CCDA) into munki
7 | Identifier
8 | com.github.mosen.pkg.Adobe.CreativeCloudAppSerialized
9 | Input
10 |
11 |
12 | MinimumVersion
13 | 1.0.0
14 | ParentRecipe
15 | com.github.mosen.pkg.Adobe.CreativeCloudApp
16 | Process
17 |
18 |
21 |
22 | Processor
23 | CreativeCloudBuildModifier
24 | Arguments
25 |
26 | suppress_ccda
27 |
28 |
29 |
30 |
31 | Processor
32 | DmgCreator
33 | Arguments
34 |
35 | dmg_root
36 | %pkg_path%
37 | dmg_path
38 | %RECIPE_CACHE_DIR%/%NAME%_Install-%user_facing_version%.dmg
39 |
40 |
41 |
42 | Processor
43 | DmgCreator
44 | Arguments
45 |
46 | dmg_root
47 | %uninstaller_pkg_path%
48 | dmg_path
49 | %RECIPE_CACHE_DIR%/%NAME%_Uninstall-%user_facing_version%.dmg
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudBuildModifier.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # Copyright 2017 Mosen/Tim Sutton
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # for debugging
18 | from pprint import pprint
19 | import os.path
20 | from autopkglib import Processor, ProcessorError
21 | from xml.etree import ElementTree
22 |
23 | __all__ = ["CreativeCloudBuildModifier"]
24 |
25 | ACC_PACKAGE_SETS = {
26 | 'AAM': [
27 | 'UWA',
28 | 'PDApp',
29 | 'D6',
30 | 'DECore',
31 | 'DWA',
32 | 'P6',
33 | 'LWA',
34 | 'CCM',
35 | 'P7',
36 | 'AdobeGCClient',
37 | 'IPC'
38 | ],
39 | 'ADC': [
40 | 'Runtime',
41 | 'Core',
42 | 'HEX',
43 | 'CEF',
44 | 'CoreExt',
45 | 'ElevationManager',
46 | 'TCC',
47 | 'Notifications',
48 | 'SignInApp'
49 | ],
50 | 'ACC': [
51 | 'HDCore',
52 | 'AppsPanel'
53 | ]
54 | }
55 |
56 |
57 | class CreativeCloudBuildModifier(Processor):
58 | """This processor parses the output of the CCP build process and makes modifications."""
59 | description = __doc__
60 | input_variables = {
61 | "pkg_path": {
62 | "required": True,
63 | "description": "The package produced by Creative Cloud Packager",
64 | },
65 | "suppress_ccda": {
66 | "description": "Suppress the installation of Creative Cloud Desktop Application.",
67 | "required": False,
68 | "default": True
69 | }
70 | }
71 | output_variables = {}
72 |
73 | def _addPackage(self, parent, name):
74 | """Add a package element w/name to a set"""
75 | pkg = ElementTree.SubElement(parent, 'package')
76 | name_el = ElementTree.SubElement(pkg, 'name')
77 | name_el.text = name
78 |
79 | def _addPackageSet(self, parent, set_name, package_names):
80 | """Add a package set given a name and list of package names"""
81 | pkg_set = ElementTree.SubElement(parent, 'packageSet')
82 | pkg_set_name = ElementTree.SubElement(pkg_set, 'name')
83 | pkg_set_name.text = set_name
84 |
85 | pkgs = ElementTree.SubElement(pkg_set, 'packages')
86 | for pkg_name in package_names:
87 | self._addPackage(pkgs, pkg_name)
88 |
89 | def _addOverrides(self, aam_info):
90 | """Add the overrideXML parts for disabling CCDA"""
91 | overridexml = ElementTree.SubElement(aam_info, 'overrideXML')
92 | override_app = ElementTree.SubElement(overridexml, 'application')
93 | package_sets = ElementTree.SubElement(override_app, 'packageSets')
94 |
95 | for sap, packages in ACC_PACKAGE_SETS.items():
96 | self._addPackageSet(package_sets, sap, packages)
97 |
98 | def _removeASUPackages(self):
99 | """Remove ASU packages"""
100 | asu_appinfo_path = os.path.join(self.env['pkg_path'], 'Contents', 'Resources', 'ASU', 'packages',
101 | 'ApplicationInfo.xml')
102 | asu_appinfo = ElementTree.parse(asu_appinfo_path)
103 | asu_appinfo_root = asu_appinfo.getroot()
104 |
105 | acc_packageset = asu_appinfo_root.find(".//packageSet[name='ACC']/packages")
106 | if acc_packageset is None:
107 | raise ProcessorError('Tried to modify ACC installation, but no packageSet element was found. This should' +
108 | 'never happen')
109 |
110 | adc_packageset = asu_appinfo_root.find(".//packageSet[name='ADC']/packages")
111 | if adc_packageset is None:
112 | raise ProcessorError('Tried to modify ACC(ADC) installation, but no packageSet element was found. This should' +
113 | 'never happen')
114 |
115 | packages_to_remove = [
116 | 'ACCC',
117 | 'Utils',
118 | 'CoreSync',
119 | 'CoreSyncExtension',
120 | 'LiveType',
121 | 'ExchangePlugin',
122 | 'DesignLibraryPlugin',
123 | 'SynKit',
124 | 'CCSyncPlugin',
125 | 'CCLibrary',
126 | 'HomePanel',
127 | 'AssetsPanel',
128 | 'FilesPanel',
129 | 'FontsPanel',
130 | 'MarketPanel',
131 | 'BehancePanel',
132 | 'SPanel',
133 | 'CCXProcess'
134 | ]
135 |
136 | # also remove package 'ADC' from 'ADC' set
137 |
138 | for to_remove in packages_to_remove:
139 | remove_pkg = acc_packageset.find(".//package[name='{}']".format(to_remove))
140 | if remove_pkg is not None:
141 | self.output('Removing package {}'.format(to_remove))
142 | self.output(remove_pkg)
143 | acc_packageset.remove(remove_pkg)
144 | else:
145 | self.output('Could not find package "{}" to remove.'.format(to_remove))
146 |
147 | with open(asu_appinfo_path, 'wb') as fd:
148 | fd.write(ElementTree.tostring(asu_appinfo_root))
149 |
150 | #
151 | #
152 | #
153 | # AppsPanel
154 | # false
155 | #
156 | #
157 | # SelfServeInstalls
158 | # false
159 | #
160 | #
161 | #
162 | def _addPanelMasking(self, root):
163 | """Disable the apps and updates panels.
164 |
165 | AppsPanel = false
166 | SelfServeInstalls = false
167 | """
168 | panels = root.findall('.//Configurations/ACCPanelMaskingConfig/config')
169 |
170 | for panel_or_feature in panels:
171 | self.output(panel_or_feature)
172 | return root
173 |
174 |
175 | def _suppressCcda(self, root):
176 | """Suppress the CCDA from being installed."""
177 | acc = root.find('.//Configurations/SuppressOptions/ACC')
178 | if acc is None:
179 | raise ProcessorError('Expected to find element .//Configurations/SuppressOptions/ACC')
180 |
181 | acc_suppressed = acc.get('suppress')
182 | self.output('Creative Cloud Desktop Application (ACC) suppressed? {}'.format(acc_suppressed))
183 |
184 | if acc_suppressed == 'true':
185 | self.output('Already suppressed, no changes required.')
186 | else:
187 | self.output('Setting ACC suppress to True')
188 | acc.set('suppress', 'true')
189 |
190 | update = root.find('.//Configurations/SuppressOptions/Update')
191 | if update is None:
192 | raise ProcessorError('Expected to find element .//Configurations/SuppressOptions/Update')
193 |
194 | update_suppressed = update.get('isEnabled')
195 | self.output('User initiated updates suppressed? {}'.format(update_suppressed))
196 |
197 | if update_suppressed == '1':
198 | self.output('Already suppressed, no changes required.')
199 | else:
200 | self.output('Setting Update isEnabled to 0')
201 | update.set('isEnabled', '0')
202 |
203 | aam_info = root.find('.//AAMInfo')
204 |
205 | package_sets = root.find('.//AAMInfo/overrideXML/application/packageSets')
206 | if package_sets is None:
207 | self.output('Need to add overrides for ACC')
208 | self._addOverrides(aam_info)
209 |
210 | return root
211 |
212 | def main(self):
213 | if not os.path.exists(self.env['pkg_path']):
214 | raise ProcessorError('The specified package does not exist: {}'.format(self.env['pkg_path']))
215 |
216 | option_xml_path = os.path.join(self.env['pkg_path'], 'Contents', 'Resources', 'optionXML.xml')
217 | option_xml = ElementTree.parse(option_xml_path)
218 | root = option_xml.getroot()
219 |
220 | if self.env.get('suppress_ccda', False):
221 | modified_root = self._suppressCcda(root)
222 | modified_root = self._addPanelMasking(modified_root)
223 |
224 | self._removeASUPackages()
225 |
226 | with open(option_xml_path, 'wb') as fd:
227 | fd.write(ElementTree.tostring(modified_root))
228 |
229 | self.output('OptionXML modified')
230 |
231 |
232 | if __name__ == "__main__":
233 | processor = CreativeCloudBuildModifier()
234 | processor.execute_shell()
235 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudFeed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # Copyright 2016-2017 Mosen/Tim Sutton
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | import sys
18 | import os.path
19 | import string
20 | import json
21 | import urllib2
22 | from tempfile import mkdtemp
23 | from urllib import urlencode
24 | from distutils.version import LooseVersion as LV
25 | from xml.etree import ElementTree
26 |
27 | # for debugging
28 | from pprint import pprint
29 |
30 | from autopkglib import Processor, ProcessorError
31 |
32 | __all__ = ["CreativeCloudFeed"]
33 |
34 | AAMEE_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/aamee/v2/products/all'
35 | BASE_URL = 'https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v4/products/all'
36 | CDN_SECURE_URL = 'https://ccmdls.adobe.com'
37 | UPDATE_DESC_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/core/v1/update/description'
38 | UPDATE_FEED_URL_MAC = 'https://swupmf.adobe.com/webfeed/oobe/aam20/mac/updaterfeed.xml'
39 | HEADERS = {'User-Agent': 'Creative Cloud', 'x-adobe-app-id': 'AUSST_4_0'}
40 |
41 |
42 | class CreativeCloudFeed(Processor):
43 | """Fetch information about product(s) from the Creative Cloud products feed."""
44 | description = __doc__
45 | input_variables = {
46 | "ccpinfo": {
47 | "required": True,
48 | "description": "Creative Cloud Packager Product(s) Information",
49 | },
50 | "channels": {
51 | "required": False,
52 | "default": "ccp_hd_2,sti",
53 | "description": "The update feed channel(s), comma separated. (default is the ccp_hd_2 and sti channels). \
54 | The first channel will be used to fetch application info",
55 | },
56 | "platforms": {
57 | "required": False,
58 | "default": "osx10,osx10-64",
59 | "description": "The deployment platform(s), comma separated. (default is osx10,osx10-64)",
60 | },
61 | "parse_proxy_xml": {
62 | "required": False,
63 | "default": False,
64 | "description": "Fetch and parse the product proxy XML which will set proxy_version in the output"
65 | },
66 | "fetch_release_notes": {
67 | "required": False,
68 | "default": False,
69 | "description": "Fetch the update release notes in the current language"
70 | },
71 | "fetch_icon": {
72 | "required": False,
73 | "default": False,
74 | "description": "Fetch the product icon to the cache directory"
75 | },
76 | "write_product_json": {
77 | "required": False,
78 | "default": True,
79 | "description": "Write a product.json file to the cache directory from the selected product fragment"
80 | }
81 | }
82 |
83 | output_variables = {
84 | "product_info_url": {
85 | "description": "Product main information URL"
86 | },
87 | "icon_url": {
88 | "description": "Icon download URL for the highest resolution available, normally 96x96."
89 | },
90 | "version": {
91 | "description": "The full length version"
92 | },
93 | "display_name": {
94 | "description": "The product full name and major version"
95 | },
96 | "manifest_url": {
97 | "description": "The URL to the product manifest"
98 | },
99 | "family": {
100 | "description": "The product family"
101 | },
102 | "minimum_os_version": {
103 | "description": "The minimum operating system version required to install this package"
104 | },
105 | "release_notes": {
106 | "description": "The update release notes if fetch_release_notes was true, otherwise empty string"
107 | },
108 | "icon_path": {
109 | "description": "Path to the downloaded icon, if fetch_icon was true."
110 | },
111 | "proxy_version": {
112 | "description": "The product version listed in the proxy file, which usually has more digits"
113 | }
114 | }
115 |
116 | def feed_url(self, channels, platforms):
117 | """Build the GET query parameters for the product feed."""
118 | params = [
119 | ('payload', 'true'),
120 | ('productType', 'Desktop'),
121 | ('_type', 'json')
122 | ]
123 | for ch in channels:
124 | params.append(('channel', ch))
125 |
126 | for pl in platforms:
127 | params.append(('platform', pl))
128 |
129 | return BASE_URL + '?' + urlencode(params)
130 |
131 | def desc_url(self, sapcode, version, platform, language):
132 | """Build the query for fetching an update description"""
133 | params = [
134 | ('name', sapcode),
135 | ('version', version),
136 | ('platform', platform),
137 | ('language', language)
138 | ]
139 |
140 | return UPDATE_DESC_URL + '?' + urlencode(params)
141 |
142 | def fetch_proxy_data(self, proxy_data_url):
143 | """Fetch the proxy data to get additional information about the product."""
144 | self.output('Fetching proxy data from {}'.format(proxy_data_url))
145 | req = urllib2.Request(proxy_data_url, headers=HEADERS)
146 | content = urllib2.urlopen(req).read()
147 |
148 | # Write out the proxy for debugging purposes
149 | with open('{}/proxy.xml'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd:
150 | fd.write(content)
151 |
152 | proxy_data = ElementTree.fromstring(content)
153 | return proxy_data
154 |
155 | def fetch_manifest(self, manifest_url):
156 | """Fetch the manifest.xml at manifest_url which contains asset download and proxy data information.
157 | Not all products have a proxy_data element
158 |
159 | :returns A tuple of (manifest, proxy) ElementTree objects
160 | """
161 | self.output('Fetching manifest.xml from {}'.format(manifest_url))
162 | req = urllib2.Request(manifest_url, headers=HEADERS)
163 | content = urllib2.urlopen(req).read()
164 |
165 | # Write out the manifest for debugging purposes
166 | with open('{}/manifest.xml'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd:
167 | fd.write(content)
168 |
169 | manifest = ElementTree.fromstring(content)
170 |
171 | proxy_data_url_el = manifest.find('asset_list/asset/proxy_data')
172 | if proxy_data_url_el is None:
173 | raise ProcessorError('Could not find proxy data URL in manifest, aborting since your package requires it.')
174 |
175 | proxy_data = self.fetch_proxy_data(proxy_data_url_el.text)
176 |
177 | return manifest, proxy_data
178 |
179 | def fetch_release_notes(self, sapcode, version, platform, language):
180 | """Fetch the update description (release notes).
181 |
182 | This returns with an xml response in the form::
183 |
184 | en_US
185 | ...
186 |
187 |
188 | It usually contains the specific bugs and features introduced in this version, not a general overview of the
189 | product.
190 | """
191 | url = self.desc_url(sapcode, version, platform, language)
192 | self.output('Fetching release notes from: {}'.format(url))
193 | req = urllib2.Request(url, headers=HEADERS)
194 | raw_data = urllib2.urlopen(req).read()
195 |
196 | return raw_data
197 |
198 | def fetch(self, channels, platforms):
199 | """Download the main feed"""
200 | url = self.feed_url(channels, platforms)
201 | self.output('Fetching from feed URL: {}'.format(url))
202 |
203 | req = urllib2.Request(url, headers=HEADERS)
204 | data = json.loads(urllib2.urlopen(req).read())
205 |
206 | return data
207 |
208 | def filter_product(self, data, sap_code, base_version, version='latest'):
209 | """Find product information from a feed dump given a single sap_code, base version and optional version."""
210 | product = {'version': '0.0.1'}
211 | channels = string.split(self.env.get('channels'), ',')
212 |
213 | #12 inputs to ccpinfo dict testing w BridgeCC
214 | for channel in data['channel']:
215 | if channel['name'] not in channels:
216 | continue
217 |
218 | for prod in channel['products']['product']:
219 | if prod['id'] != sap_code:
220 | continue
221 |
222 | if base_version and prod['platforms']['platform'][0]['languageSet'][0].get('baseVersion') != base_version:
223 | continue
224 |
225 | if 'version' not in prod:
226 | self.output('product has no version: {}'.format(prod['displayName']))
227 | continue
228 |
229 | if version == "latest":
230 | if LV(prod['version']) > LV(product['version']):
231 | product = prod
232 | else:
233 | if prod['version'] == version:
234 | product = prod
235 |
236 | if 'platforms' not in product:
237 | return None
238 |
239 | return product
240 |
241 | def fetch_extended_product_info(self, product, platform, cdn):
242 | """Fetch extended information about a product such as: manifest,
243 | proxy (if available), release notes, and icon"""
244 | extended_info = {}
245 | channels = string.split(self.env.get('channels'), ',')
246 |
247 | # Fetch Icon
248 | if 'productIcons' in product:
249 | largest_width = 0
250 | largest_icon_url = None
251 | for icon in product['productIcons'].get('icon', []):
252 | self.output('Considering icon: {}'.format(icon))
253 | w, h = icon.get('size', '0x0').split('x', 2)
254 | if int(w) > largest_width:
255 | largest_width = int(w)
256 | largest_icon_url = icon.get('value')
257 |
258 | self.env['icon_url'] = largest_icon_url
259 |
260 | if 'icon_url' in self.env and self.env.get('fetch_icon', 'false').lower() == 'true':
261 | self.output('Fetching icon from {}'.format(self.env['icon_url']))
262 | req = urllib2.Request(self.env['icon_url'], headers=HEADERS)
263 | content = urllib2.urlopen(req).read()
264 |
265 | with open('{}/Icon.png'.format(self.env['RECIPE_CACHE_DIR']), 'w+') as fd:
266 | fd.write(content)
267 |
268 | extended_info['icon_path'] = '{}/Icon.png'.format(self.env['RECIPE_CACHE_DIR'])
269 | else:
270 | self.output('An icon was not requested or the url did not exist.')
271 | extended_info['icon_path'] = ''
272 |
273 | # Fetch Manifest + Proxy
274 | if 'urls' in platform['languageSet'][0] and 'manifestURL' in platform['languageSet'][0]['urls']:
275 | extended_info['manifest_url'] = '{}{}'.format(
276 | cdn[channels[0]]['secure'],
277 | platform['languageSet'][0]['urls'].get('manifestURL')
278 | )
279 |
280 | if self.env.get('parse_proxy_xml', False):
281 | self.output('Processor will fetch manifest and proxy xml')
282 | manifest, proxy = self.fetch_manifest(extended_info['manifest_url'])
283 | product_version_el = proxy.find('InstallerProperties/Property[@name="ProductVersion"]')
284 | if product_version_el is None:
285 | raise ProcessorError('Could not find ProductVersion in proxy data, aborting.')
286 | else:
287 | self.output('Found version in proxy.xml: {}'.format(product_version_el.text))
288 | extended_info['proxy_version'] = product_version_el.text
289 | else:
290 | extended_info['proxy_version'] = ''
291 | else:
292 | self.output('Did not find a manifest.xml in the product json data')
293 |
294 | # Fetch Release Notes
295 | if self.env.get('fetch_release_notes', 'false').lower() == 'true':
296 | self.output('Processor will fetch update release notes')
297 | desc = self.fetch_release_notes(product['id'], product['version'], 'osx10-64', 'en_US')
298 | rn_etree = ElementTree.fromstring(desc)
299 | release_notes_el = rn_etree.find('UpdateDescription')
300 |
301 | if release_notes_el is None:
302 | raise ProcessorError('Could not find UpdateDescription in release notes')
303 |
304 | extended_info['release_notes'] = release_notes_el.text
305 | else:
306 | extended_info['release_notes'] = ''
307 |
308 | return extended_info
309 |
310 | def cache_product_info(self, input_product, output_product):
311 | """Cache the feed result (outputProduct) based on parameters specified in inputProduct."""
312 | self.env['download_changed'] = True
313 | cache_json_path = '{0}/{1}_{2}_{3}.json'.format(
314 | self.env['RECIPE_CACHE_DIR'],
315 | input_product['sapCode'],
316 | input_product.get('baseVersion', ''),
317 | input_product.get('version', 'latest')
318 | )
319 |
320 | # Check against last result if available
321 | if os.path.exists(cache_json_path):
322 | with open(cache_json_path, 'r') as fd:
323 | content = fd.read()
324 | data = json.loads(content)
325 | if data.get('version', '') == output_product.get('version'):
326 | self.output('The feed version matches the last fetched version, no download is required')
327 | self.env['download_changed'] = False
328 | else:
329 | self.output('The feed version has changed from the last fetch, download is required')
330 |
331 | # Feed processor uses this to detect whether there is a newer feed version.
332 | if self.env.get('write_product_json', True):
333 | self.output('Caching product information to {}'.format(cache_json_path))
334 | with open(cache_json_path, 'w+') as fd:
335 | fd.write(json.dumps(output_product))
336 |
337 | def validate_input(self):
338 | """Validate processor inputs"""
339 | if 'ccpinfo' not in self.env:
340 | raise ProcessorError('No ccpinfo dict supplied to CreativeCloudFeed. Please check your recipe.')
341 |
342 | ccpinfo = self.env['ccpinfo']
343 | if 'Products' not in ccpinfo or len(ccpinfo['Products']) == 0:
344 | raise ProcessorError('ccpinfo does not specify any products. Please check your recipe.')
345 |
346 | for prod in ccpinfo['Products']:
347 | if 'sapCode' not in prod:
348 | raise ProcessorError('ccpinfo product did not contain a SAP Code')
349 |
350 |
351 | def main(self):
352 | ccpinfo = self.env['ccpinfo']
353 | channels = string.split(self.env.get('channels'), ',')
354 | platforms = string.split(self.env.get('platforms'), ',')
355 |
356 | data = self.fetch(channels, platforms)
357 | self.validate_input()
358 |
359 | channel_cdn = {}
360 | for channel in data['channel']:
361 | if channel['name'] in channels:
362 | channel_cdn[channel['name']] = channel.get('cdn')
363 |
364 | # Resolve actual build versions from the feed
365 | products = []
366 | for product_info in ccpinfo['Products']:
367 | sapcode = product_info['sapCode']
368 | baseversion = product_info.get('baseVersion', '')
369 | version = product_info.get('version', 'latest')
370 |
371 | product = self.filter_product(data, sapcode, baseversion, version)
372 | if product is None:
373 | raise ProcessorError(
374 | 'No package matched the SAP code ({0}), base version ({1}), '
375 | 'and version ({2}) combination you specified.'.format(sapcode, baseversion, version)
376 | )
377 |
378 | self.output('Found matching product {}, version: {}'.format(product.get('displayName'),
379 | product.get('version')))
380 |
381 | product_info['version'] = product['version']
382 | product_info['requestedVersion'] = version
383 | products.append(product)
384 | self.cache_product_info(product_info, product)
385 |
386 | if len(products) == 1: # Single product, will use normal version, notes, icon
387 | product = products[0]
388 |
389 | first_platform = {}
390 | for platform in product['platforms']['platform']:
391 | if platform['id'] in platforms:
392 | first_platform = platform
393 | break
394 |
395 | if first_platform.get('packageType') == 'RIBS':
396 | raise ProcessorError('This process does not support RIBS style packages.')
397 |
398 | if len(first_platform['systemCompatibility']['operatingSystem']['range']) > 0:
399 | compatibility_range = first_platform['systemCompatibility']['operatingSystem']['range'][0]
400 | # systemCompatibility currently has values like:
401 | # 10.x.0-
402 | # 10.10- (no minor version specified)
403 | # (empty array)
404 | self.env['minimum_os_version'] = compatibility_range.split('-')[0]
405 | else:
406 | # hacky workaround to avoid packager bailing when there is no minimum os version
407 | self.env['minimum_os_version'] = ''
408 |
409 | # output variable naming has been kept as close to pkginfo names as possible in order to feed munkiimport
410 | self.env['product_info_url'] = product.get('productInfoPage')
411 | self.env['version'] = product.get('version')
412 | self.env['display_name'] = product.get('displayName')
413 |
414 | extended_info = self.fetch_extended_product_info(product, first_platform, channel_cdn)
415 | for k, v in extended_info.items():
416 | self.env[k] = v
417 |
418 | else: # more than one product: indeterminate version, concatenated notes, no icon
419 | raise ProcessorError('Multi product packages not yet supported')
420 |
421 |
422 | if __name__ == "__main__":
423 | processor = CreativeCloudFeed()
424 | processor.execute_shell()
425 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudPackager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # Copyright 2016-2017 Mosen/Tim Sutton
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # pylint: disable=locally-disabled, import-error, invalid-name, no-member
18 |
19 | # for now
20 | # pylint: disable=line-too-long
21 |
22 | import os
23 | import shutil
24 | import subprocess
25 | import uuid
26 | import FoundationPlist
27 |
28 | from xml.etree import ElementTree
29 | from Foundation import CFPreferencesCopyAppValue, CFPreferencesSetAppValue
30 |
31 | from autopkglib import Processor, ProcessorError
32 |
33 | __all__ = ["CreativeCloudPackager"]
34 |
35 | # https://helpx.adobe.com/creative-cloud/packager/ccp-automation.html
36 | #
37 | # https://github.com/timsutton/adobe-ccp-automation/blob/master/ccp_auto
38 |
39 | CUSTOMER_TYPES = ["enterprise", "team"]
40 | CCP_PREFS_FILE = os.path.expanduser(
41 | "~/Library/Application Support/Adobe/CCP/CCPPreferences.xml")
42 |
43 | CCP_ERROR_MSGS = {
44 | "CustomerTypeMismatchError": \
45 | ("Please check that your organization is of the correct "
46 | "type, one of ({}).".format(', '.join(CUSTOMER_TYPES))),
47 | "TronWelcomeInputValidationError": \
48 | ("Please check that your organizationName matches one to which your "
49 | "CCP-signed-in user ""belongs."),
50 | "TronSerialNumberValidationError": \
51 | ("Serial number validation failed."),
52 | }
53 |
54 | # Note: TronWelcomeInputValidationError can also happen if a file or folder
55 | # already exists at the package path given. Sample result output in that case:
56 | #
57 | #
58 | # 2
59 | # false
60 | # TronWelcomeInputValidationError
61 | #
62 | #
63 |
64 | # Note: if the user supplies an incorrect SAP Code or a product that cannot be packaged individually (like AAM)
65 | # you will receive this error:
66 | #
67 | #
68 | # 3
69 | # false
70 | # productNotFound
71 | #
72 | #
73 |
74 | class CreativeCloudPackager(Processor):
75 | """Create and execute a CCP automation file. The package output will always be the autopkg cache directory"""
76 | description = "Runs the CCP packager."
77 | input_variables = {
78 | "ccpinfo": {
79 | "required": True,
80 | "description": "Creative Cloud Packager Product(s) Information",
81 | },
82 | "package_name": {
83 | "required": True,
84 | "description": "The output package name",
85 | },
86 | }
87 |
88 | output_variables = {
89 | "pkg_path": {
90 | "description": "Path to the built bundle-style CCP installer pkg.",
91 | },
92 | "uninstaller_pkg_path": {
93 | "description": "Path to the built bundle-style CCP uninstaller pkg.",
94 | },
95 | "ccp_path": {
96 | "description": "Path to the .ccp file output from the build process."
97 | },
98 | "package_info_text": {
99 | "description": "Text notes about which packages and updates are included in the pkg."
100 | },
101 | "ccp_version": {
102 | "description": "Version of CCP tools used to build the package."
103 | },
104 | "creative_cloud_packager_summary_result": {
105 | "description": "Description of interesting results."
106 | },
107 | }
108 |
109 | def ccp_preferences(self):
110 | """Get information about the currently signed-in CCP user, if available."""
111 | prefs_path = os.path.expanduser(CCP_PREFS_FILE)
112 | prefs_elem = ElementTree.parse(prefs_path).getroot()
113 |
114 | prefs = {}
115 | user_type_elem = prefs_elem.find('AAMEEPreferences/Preference/Screen/userType')
116 | if user_type_elem is not None:
117 | # convert 'FOO_CUSTOMER_TYPE' into 'foo'
118 | prefs["customer_type"] = user_type_elem.text.lower().split('_')[0]
119 | return prefs
120 |
121 | def automation_xml(self):
122 | """Returns the complete pretty-formatted XML string for a CCP automation
123 | session."""
124 | # params = self.automation_manifest_from_ccpinfo()
125 | params = dict(self.env['ccpinfo'])
126 |
127 | # 1. If you specified IncludeUpdates then that value will override everything.
128 | # 2. If any specified product has version 'latest' then IncludeUpdates is true, and CCP will fetch the latest
129 | # available product update.
130 | # 3. Otherwise, You only get the baseVersion you entered.
131 | if 'IncludeUpdates' not in params:
132 | if any([product.get('requestedVersion', None) == 'latest' for product in params['Products']]):
133 | self.output("At least one product has requested version 'latest'. This means we are enabling IncludeUpdates for CCP")
134 | params['IncludeUpdates'] = True
135 | else:
136 | params['IncludeUpdates'] = False
137 |
138 | # add additional parameters for which there's no need for the user to
139 | # supply in the 'ccpinfo' input
140 | params.update({
141 | 'packageName': self.env['package_name'],
142 | 'outputLocation': self.env['RECIPE_CACHE_DIR'],
143 | 'packaging_job_id': str(uuid.uuid4()),
144 | 'is64Bit': True,
145 | })
146 |
147 | # if params.get('serial_number') and scrub_serial:
148 | # params['serial_number'] = 'REDACTED'
149 |
150 | # Begin assembling XML Element
151 | pkg_elem = ElementTree.Element('CreatePackage')
152 | # add some hardcoded elements
153 | category = ElementTree.Element('ProductCategory')
154 | category.text = 'Custom'
155 | pkg_elem.append(category)
156 | # language - must be included even if matchOSLanguage is true
157 |
158 | lang = ElementTree.Element('Language')
159 | lang.append(ElementTree.Element('id'))
160 | lang.find('id').text = params['Language']
161 | pkg_elem.append(lang)
162 | del params['Language']
163 |
164 | # Input keys now match the target XML to avoid transforming
165 | for param, value in params.iteritems():
166 | if param == 'Products':
167 | continue
168 |
169 | elem = ElementTree.Element(param)
170 | if isinstance(value, bool):
171 | value = str(value).lower()
172 | elem.text = value
173 |
174 | pkg_elem.append(elem)
175 |
176 | # Products
177 | products = ElementTree.Element('Products')
178 | for prod in params['Products']:
179 | product = ElementTree.Element('Product')
180 | sap = ElementTree.Element('sapCode')
181 | sap.text = prod['sapCode']
182 | ver = ElementTree.Element('version')
183 | ver.text = prod['baseVersion']
184 | product.append(sap)
185 | product.append(ver)
186 | products.append(product)
187 | pkg_elem.append(products)
188 |
189 | # # serial
190 | # if params.get('serialNumber'):
191 | # self.output('Adding serial number to ccp_automation xml')
192 | # serial = ElementTree.Element('serialNumber')
193 | # serial.text = params['serial_number']
194 | # pkg_elem.append(serial)
195 |
196 | xml_root = ElementTree.Element('CCPPackage')
197 | xml_root.append(pkg_elem)
198 |
199 | # run it through `xmllint --format` just to save it pretty :/
200 | xml_string = ElementTree.tostring(xml_root, encoding='utf8', method='xml')
201 | proc = subprocess.Popen(['/usr/bin/xmllint', '--format', '-'],
202 | stdin=subprocess.PIPE,
203 | stdout=subprocess.PIPE)
204 | out, err = proc.communicate(xml_string)
205 | if proc.returncode:
206 | raise ProcessorError("Unexpected error from running XML output through "
207 | "xmllint. Stderr output:\n%s" % err)
208 | return out
209 |
210 | def set_customer_type(self, ccpinfo):
211 | # Set the customer type, using CCP's preferences if none provided
212 | if not ccpinfo.get("customerType"):
213 | ccp_prefs = self.ccp_preferences()
214 | self.env['customer_type'] = ccp_prefs.get("customer_type")
215 | if not self.env.get("customer_type"):
216 | raise ProcessorError(
217 | "No customer_type input provided and unable to read one "
218 | "from %s" % CCP_PREFS_FILE)
219 | self.output("Using customer type '%s' found in CCPPreferences: %s'"
220 | % (self.env['customer_type'], CCP_PREFS_FILE))
221 |
222 | def is_ccp_running(self):
223 | """Determine whether CCP is already running. This would prevent us from actually running the automation XML."""
224 | status = subprocess.call(['/usr/bin/pgrep', '-q', 'PDApp'])
225 | return status == 0
226 |
227 | def check_and_disable_appnap_for_pdapp(self):
228 | """Log a warning if AppNap isn't disabled on the system."""
229 | appnap_disabled = CFPreferencesCopyAppValue(
230 | 'NSAppSleepDisabled',
231 | 'com.adobe.PDApp')
232 | if not appnap_disabled:
233 | self.output("WARNING: A bug in Creative Cloud Packager makes "
234 | "it likely to stall indefinitely whenever the app "
235 | "window is hidden or obscured due to App Nap. To "
236 | "prevent this, we're setting a user preference to "
237 | "disable App Nap for just the "
238 | "Adobe PDApp application. This can be undone using "
239 | "this command: 'defaults delete com.adobe.PDApp "
240 | "NSAppSleepDisabled")
241 | CFPreferencesSetAppValue(
242 | 'NSAppSleepDisabled',
243 | True,
244 | 'com.adobe.PDApp')
245 |
246 | def validate_input(self):
247 | """Validate input variables will produce something meaningful."""
248 | ccpinfo = self.env['ccpinfo']
249 |
250 | if 'Products' not in ccpinfo or len(ccpinfo['Products']) == 0:
251 | raise ProcessorError('ccpinfo does not specify any products. Please check your recipe.')
252 |
253 | for prod in ccpinfo['Products']:
254 | if 'sapCode' not in prod:
255 | raise ProcessorError('ccpinfo product did not contain a SAP Code')
256 |
257 | if 'organizationName' not in ccpinfo or ccpinfo['organizationName'] == 'ADMIN_PLEASE_CHANGE':
258 | raise ProcessorError('No organization name specified in recipe.')
259 |
260 | if ccpinfo['customerType'] not in CUSTOMER_TYPES:
261 | raise ProcessorError(
262 | "customerType input variable must be one of : %s" %
263 | ", ".join(CUSTOMER_TYPES))
264 |
265 | if ccpinfo['customerType'] != 'enterprise' and ccpinfo.get('serialNumber'):
266 | raise ProcessorError(
267 | ("Serial number was given, but serial numbers are only for "
268 | "use with 'enterprise' customer types."))
269 |
270 | def check_ccda_installed(self):
271 | """Check and raise a ProcessorError if the CCDA is installed, as CCP should
272 | never build packages on a system with the CCDA installed"""
273 | ccda_path = '/Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app'
274 | if os.path.isdir(ccda_path):
275 | raise ProcessorError(
276 | ("The Adobe Creative Cloud Desktop App was detected at '%s'. "
277 | "This recipe will only run on systems without it installed, "
278 | "as it can otherwise cause major issues with built packages. "
279 | "It can be uninstalled using the Uninstaller located at "
280 | "'/Applications/Utilities/Adobe Creative Cloud'.") % ccda_path)
281 |
282 | def main(self):
283 | if self.env.get("ALLOW_CCDA_INSTALLED", False):
284 | self.check_ccda_installed()
285 |
286 | # establish some of our expected build paths
287 | expected_output_root = os.path.join(self.env["RECIPE_CACHE_DIR"], self.env["package_name"])
288 | self.env["pkg_path"] = os.path.join(expected_output_root, "Build/%s_Install.pkg" % self.env["package_name"])
289 | self.env["uninstaller_pkg_path"] = os.path.join(expected_output_root,
290 | "Build/%s_Uninstall.pkg" % self.env["package_name"])
291 |
292 | saved_automation_xml_path = os.path.join(expected_output_root,
293 | '.ccp_automation_input.xml')
294 | automation_manifest_plist_path = os.path.join(expected_output_root,
295 | '.autopkg_manifest.plist')
296 | self.set_customer_type(self.env['ccpinfo'])
297 |
298 | # ccpinfo Needs pre-processing before comparison to old run
299 | new_manifest = self.automation_xml()
300 |
301 | # Handle any pre-existing package at the expected location, and end early if it matches our
302 | # input manifest
303 | if os.path.exists(automation_manifest_plist_path):
304 | existing_manifest = FoundationPlist.readPlist(automation_manifest_plist_path)
305 | self.output("Found existing CCP package build automation info, comparing")
306 | self.output("existing build: %s" % existing_manifest)
307 | self.output("current build: %s" % new_manifest)
308 | if new_manifest == existing_manifest:
309 | self.output("Returning early because we have an existing package "
310 | "with the same parameters.")
311 | return
312 |
313 | # Going forward with building, set up or clear needed directories
314 | xml_workdir = os.path.join(self.env["RECIPE_CACHE_DIR"], 'automation_xml')
315 | if not os.path.exists(xml_workdir):
316 | os.mkdir(xml_workdir)
317 | if os.path.isdir(expected_output_root):
318 | shutil.rmtree(expected_output_root)
319 |
320 | # using .xml as a suffix because CCP's automation mode creates a '_results.xml' file with the assumption
321 | # that the input ends in '.xml'
322 | xml_path = os.path.join(xml_workdir, 'ccp_automation_%s.xml' % self.env['package_name'])
323 | with open(xml_path, 'w') as fd:
324 | fd.write(new_manifest)
325 |
326 | if self.is_ccp_running():
327 | raise ProcessorError(
328 | "You cannot start a Creative Cloud Packager automation workflow " +
329 | "if Creative Cloud Packager is already running. Please quit CCP and start this recipe again."
330 | )
331 |
332 | self.check_and_disable_appnap_for_pdapp()
333 |
334 | cmd = [
335 | '/Applications/Utilities/Adobe Application Manager/core/Adobe Application Manager.app/Contents/MacOS/PDApp',
336 | '--appletID=CCP_UI',
337 | '--appletVersion=1.0',
338 | '--workflow=ccp',
339 | '--automationMode=ccp_automation',
340 | '--pkgConfigFile=%s' % xml_path]
341 | self.output("Executing CCP build command: %s" % " ".join(cmd))
342 | proc = subprocess.Popen(cmd,
343 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
344 | out, _ = proc.communicate()
345 | if out:
346 | self.output("CCP Output: %s" % out)
347 | exitcode = proc.returncode
348 | self.output("CCP Exited with status {}".format(exitcode))
349 |
350 | results_file = os.path.join(os.path.dirname(xml_path), os.path.splitext(xml_path)[0] + '_result.xml')
351 | results_elem = ElementTree.parse(results_file).getroot()
352 | if results_elem.find('error') is not None:
353 | # Build an AutoPkg error message with help to diagnose
354 | # possible build failures
355 | autopkg_error_msg = "CCP package build reported failure.\n"
356 | err_msg_type = results_elem.find('error/errorMessage')
357 | if err_msg_type is not None:
358 | autopkg_error_msg += "Error type: '%s' - " % err_msg_type.text
359 | if err_msg_type.text in CCP_ERROR_MSGS:
360 | autopkg_error_msg += CCP_ERROR_MSGS[err_msg_type.text] + "\n"
361 | autopkg_error_msg += (
362 | "Please inspect the PDApp log file at: %s. 'results' XML file "
363 | "contents follow: \n%s" % (
364 | os.path.expanduser("~/Library/Logs/PDApp.log"),
365 | open(results_file, 'r').read()))
366 |
367 | raise ProcessorError(autopkg_error_msg)
368 |
369 | if results_elem.find('success') is None:
370 | raise ProcessorError(
371 | "Unexpected result from CCP, 'results' XML file contents follow: \n{}".format(
372 | open(results_file, 'r').read()
373 | )
374 | )
375 |
376 | # Sanity-check that we really do have our install package!
377 | if not os.path.exists(self.env["pkg_path"]):
378 | raise ProcessorError(
379 | "CCP exited successfully, but no expected installer package "
380 | "at %s exists." % self.env["pkg_path"])
381 |
382 | # Save both the automation XML for posterity and our manifest plist for
383 | # later comparison
384 | shutil.copy(xml_path, saved_automation_xml_path)
385 | # TODO: we aren't scrubbing the automation XML file at all
386 | FoundationPlist.writePlist(
387 | self.env['ccpinfo'],
388 | automation_manifest_plist_path)
389 |
390 | # Save PackageInfo.txt
391 | packageinfo = os.path.join(expected_output_root, "PackageInfo.txt")
392 | if os.path.exists(packageinfo):
393 | self.env["package_info_text"] = open(packageinfo, 'r').read()
394 |
395 | ccp_path = os.path.join(expected_output_root, 'Build/{}.ccp'.format(self.env["package_name"]))
396 | if os.path.exists(ccp_path):
397 | self.env["ccp_path"] = ccp_path
398 |
399 | option_xml_root = ElementTree.parse(os.path.join(
400 | self.env["pkg_path"], 'Contents/Resources/optionXML.xml')).getroot()
401 |
402 | # Save the CCP build version
403 | self.env["ccp_version"] = ""
404 | ccp_version = option_xml_root.find("prodVersion")
405 | if ccp_version is None:
406 | self.output(
407 | "WARNING: Didn't find expected 'prodVersion' key (CCP "
408 | "version) in optionXML.xml")
409 | self.env["ccp_version"] = ccp_version.text
410 |
411 | if len(self.env['ccpinfo']['Products']) == 1:
412 | built_products = self.env['ccpinfo']['Products'][0]['sapCode']
413 | else:
414 | built_products = '(multiple)'
415 | self.env["creative_cloud_packager_summary_result"] = {
416 | 'summary_text': 'The following CCP packages were built:',
417 | 'report_fields': ['display_name', 'product_id', 'version', 'pkg_path'],
418 | 'data': {
419 | 'display_name': self.env['display_name'],
420 | 'product_id': built_products,
421 | 'version': self.env['version'],
422 | 'pkg_path': self.env['pkg_path'],
423 | }
424 | }
425 |
426 | if __name__ == "__main__":
427 | processor = CreativeCloudPackager()
428 | processor.execute_shell()
429 |
--------------------------------------------------------------------------------
/Adobe/CreativeCloudVersioner.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # Copyright 2017 Mosen/Tim Sutton/Macmule
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # pylint: disable=locally-disabled, import-error, invalid-name, no-member
18 |
19 | # for now
20 | # pylint: disable=line-too-long
21 |
22 | import json
23 | import os
24 | import re
25 | import zipfile
26 |
27 | from xml.etree import ElementTree
28 |
29 | import FoundationPlist
30 | from autopkglib import Processor, ProcessorError
31 |
32 | __all__ = ["CreativeCloudVersioner"]
33 |
34 |
35 | class CreativeCloudVersioner(Processor):
36 | """Parses generated CCP installers for detailed application path and bundle
37 | version info, for use in Munki installs info and JSS application inventory
38 | info for Smart Group templates. 'version' is used to store the bundle version
39 | because the JSS recipe uses app inventory version info for the Smart Group
40 | criteria"""
41 | description = __doc__
42 | input_variables = {
43 |
44 | }
45 |
46 | output_variables = {
47 | "additional_pkginfo": {
48 | "description":
49 | "Some pkginfo fields extracted from the Adobe metadata.",
50 | },
51 | "jss_inventory_name": {
52 | "description": "Application title for jamf pro smart group criteria.",
53 | },
54 | "user_facing_version": {
55 | "description": ("The version which would be seen in the application's About window, "
56 | "and which is referred to in documentation and marketing materials."),
57 | },
58 | "version": {
59 | "description": ("The value of CFBundleShortVersionString for the app bundle. "
60 | "This may match user_facing_version, but it may also be more "
61 | "specific and add another version component."),
62 | },
63 | }
64 |
65 | def main(self):
66 | """
67 | Determine a pkginfo, version and jss inventory name from the created package.
68 |
69 | Inputs:
70 | ccpinfo: The CCPInfo dict which was included in the original recipe.
71 | version: The desired version
72 | Outputs:
73 | user_facing_version: The version which would be seen in the application's About window.
74 | prod: The CCPInfo products dictionary
75 | sapCode: The SAP Code of the product
76 | ccpVersion:
77 | app_json: The path of the Application.json file that CCP produced as part of the build process
78 |
79 | """
80 | ccpinfo = self.env["ccpinfo"]
81 | # 'version' contains that which was extracted from the feed, which is
82 | # actually what we want as a user-facing version, so just grab it
83 | # immediately
84 | self.env["user_facing_version"] = self.env["version"]
85 | self.env["prod"] = ccpinfo["Products"]
86 | self.env["sapCode"] = self.env["prod"][0]["sapCode"]
87 | self.output("sapCode: %s" % self.env["sapCode"])
88 | self.env["ccpVersion"] = self.env["prod"][0]["version"]
89 | self.output("ccpVersion: %s" % self.env["ccpVersion"])
90 | self.env["app_json"] = os.path.join(self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], "Application.json")
91 | # If Application.json exists, we"re looking at a HD installer
92 | if os.path.exists(self.env["app_json"]):
93 | self.output("Installer is HyperDrive")
94 | self.output("app_json: %s" % self.env["app_json"])
95 | self.process_hd_installer()
96 | else:
97 | self.output("Installer is RIBS, since path does not exist: {}".format(self.env["app_json"]))
98 | # If not a HD installer
99 | # Legacy Installers: PKG"s but for old titles
100 | # RIBS: SPGD, LTRM, FLBR, KETK
101 | # If the above get moved to HD installs, won"t hit this.
102 | # Acrobat is a "current" title with a PKG installer we can extract needed
103 | # metadata from
104 | if self.env["sapCode"] != "APRO":
105 | self.process_ribs_installer(self.env['pkg_path'], sap_code_hint=self.env['sapCode'])
106 | else:
107 | self.env["proxy_xml"] = os.path.join(self.env["pkg_path"], "Contents/Resources/Setup", self.env["sapCode"] + self.env["ccpVersion"], "proxy.xml")
108 | if not os.path.exists(self.env["proxy_xml"]):
109 | raise ProcessorError("APRO selected, proxy.xml not found at %s" % self.env["proxy_xml"])
110 | else:
111 | self.process_apro_installer()
112 |
113 | def process_apro_installer(self):
114 | """ Process APRO installer """
115 | self.output("Processing Acrobat installer")
116 | self.output("proxy_xml: %s" % self.env["proxy_xml"])
117 | tree = ElementTree.parse(self.env["proxy_xml"])
118 | root = tree.getroot()
119 |
120 | app_bundle_text = root.findtext("./ThirdPartyComponent/Metadata/Properties/Property[@name='path']")
121 | app_bundle = app_bundle_text.split('/')[1]
122 | self.output("app_bundle: %s" % app_bundle)
123 |
124 | app_path_text = root.findtext('./InstallDir/Platform')
125 | self.output(app_path_text)
126 | app_path = app_path_text.split('/')[1]
127 | self.output("app_path: %s" % app_path)
128 |
129 | installed_path = os.path.join("/Applications", app_path, app_bundle)
130 | self.output("installed_path: %s" % installed_path)
131 |
132 | app_version = root.findtext('./InstallerProperties/Property[@name="ProductVersion"]')
133 | self.output("app_version: %s" % app_version)
134 |
135 | # Now we have the deets, let"s use them
136 | self.create_pkginfo(app_bundle, app_version, installed_path)
137 |
138 | def process_hd_installer(self):
139 | """Process HD installer
140 |
141 | Inputs:
142 | app_json: Path to the Application JSON that was extracted from the feed.
143 | """
144 | self.output("Processing HD installer")
145 | with open(self.env["app_json"]) as json_file:
146 | load_json = json.load(json_file)
147 |
148 | # AppLaunch is not always in the same format, but is splittable
149 | if 'AppLaunch' in load_json: # Bridge CC is HD but does not have AppLaunch
150 | app_launch = load_json["AppLaunch"]
151 | self.output("app_launch: %s" % app_launch)
152 | app_details = list(re.split("/", app_launch))
153 | if app_details[2].endswith(".app"):
154 | app_bundle = app_details[2]
155 | app_path = app_details[1]
156 | else:
157 | app_bundle = app_details[1]
158 | app_path = list(re.split("/", (load_json["InstallDir"]["value"])))[1]
159 | self.output("app_bundle: %s" % app_bundle)
160 | self.output("app_path: %s" % app_path)
161 |
162 | installed_path = os.path.join("/Applications", app_path, app_bundle)
163 | self.output("installed_path: %s" % installed_path)
164 |
165 | zip_file = load_json["Packages"]["Package"][0]["PackageName"]
166 | self.output("zip_file: %s" % zip_file)
167 |
168 | zip_path = os.path.join(self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], zip_file + ".zip")
169 | self.output("zip_path: %s" % zip_path)
170 | with zipfile.ZipFile(zip_path, mode="r") as myzip:
171 | with myzip.open(zip_file + ".pimx") as mytxt:
172 | txt = mytxt.read()
173 | tree = ElementTree.fromstring(txt)
174 | # Loop through .pmx's Assets, look for target=[INSTALLDIR], then grab Assets Source.
175 | # Break when found .app/Contents/Info.plist
176 | for elem in tree.findall("Assets"):
177 | for i in elem.getchildren():
178 | if i.attrib["target"].upper().startswith("[INSTALLDIR]"):
179 | bundle_location = i.attrib["source"]
180 | else:
181 | continue
182 | if not bundle_location.startswith("[StagingFolder]"):
183 | continue
184 | else:
185 | bundle_location = bundle_location[16:]
186 | if bundle_location.endswith(".app"):
187 | zip_bundle = os.path.join("1", bundle_location, "Contents/Info.plist")
188 | else:
189 | zip_bundle = os.path.join("1", bundle_location, app_bundle, "Contents/Info.plist")
190 | try:
191 | with myzip.open(zip_bundle) as myplist:
192 | plist = myplist.read()
193 | data = FoundationPlist.readPlistFromString(plist)
194 | app_version = data["CFBundleShortVersionString"]
195 | #app_identifier = data["CFBundleIdentifier"]
196 | self.output("staging_folder: %s" % bundle_location)
197 | self.output("staging_folder_path: %s" % zip_bundle)
198 | self.output("app_version: %s" % app_version)
199 | self.output("app_bundle: %s" % app_bundle)
200 | #self.output("app_identifier: %s" % app_identifier)
201 | break
202 | except:
203 | continue
204 |
205 | # Now we have the deets, let's use them
206 | self.create_pkginfo(app_bundle, app_version, installed_path)
207 |
208 | def process_ribs_installer(self, pkg_path, sap_code_hint=None):
209 | """Extract version number of RIBS based package.
210 |
211 | Args:
212 | pkg_path (str): Path to the package that was produced
213 | sap_code_hint (str): hint the sap code of the "main" package, to extract the version from.
214 | """
215 | option_xml_path = os.path.join(pkg_path, 'Contents', 'Resources', 'optionXML.xml')
216 | # ribs_root = os.path.join(pkg_path, 'Contents', 'Resources', 'Setup')
217 |
218 | option_xml = ElementTree.parse(option_xml_path)
219 | main_media = None
220 | for media in option_xml.findall('.//Medias/Media'): # Media refers to RIBS media only. HD is in HDMedia
221 | if media.findtext('SAPCode') == sap_code_hint:
222 | main_media = media
223 | break
224 |
225 | if main_media is None:
226 | raise ProcessorError('Could not find main RIBS package indicated by SAP Code {}'.format(sap_code_hint))
227 |
228 | # media_path = os.path.join(ribs_root, main_media.findtext('TargetFolderName'))
229 | # if not os.path.exists(media_path):
230 | # raise ProcessorError('Could not find Media for RIBS package in path: {}'.format(media_path))
231 |
232 | self.create_pkginfo('NOT_SUPPORTED', main_media.findtext('prodVersion'), '')
233 |
234 | def create_pkginfo(self, app_bundle, app_version, installed_path):
235 | """Create pkginfo with found details
236 |
237 | Args:
238 | app_bundle (str): Bundle name
239 | app_version (str): Bundle version
240 | installed_path (str): The path where the installed item will be installed.
241 | """
242 | self.env["version"] = app_version
243 | self.env["jss_inventory_name"] = app_bundle
244 |
245 | pkginfo = {
246 | 'display_name': self.env["display_name"],
247 | 'minimum_os_version': self.env["minimum_os_version"]
248 | }
249 |
250 | # Allow the user to provide an installs array that prevents CreativeCloudVersioner from overriding it.
251 | if 'pkginfo' not in self.env or 'installs' not in self.env['pkginfo']:
252 | pkginfo['installs'] = [{
253 | 'CFBundleShortVersionString': self.env['version'],
254 | 'path': installed_path,
255 | 'type': 'application',
256 | 'version_comparison_key': 'CFBundleShortVersionString',
257 | }]
258 |
259 | self.env["additional_pkginfo"] = pkginfo
260 | self.output("additional_pkginfo: %s" % self.env["additional_pkginfo"])
261 |
262 |
263 | if __name__ == "__main__":
264 | processor = CreativeCloudVersioner()
265 |
--------------------------------------------------------------------------------
/Adobe/PolicyTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Install Latest %PROD_NAME%
4 | true
5 | Ongoing
6 |
7 | %POLICY_CATEGORY%
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | true
21 | Install %VERSION%
22 | %SELF_SERVICE_DESCRIPTION%
23 |
24 |
25 | true
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Adobe/SmartGroupTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 | %group_name%
3 | true
4 |
5 |
6 | Application Title
7 | 0
8 | and
9 | is
10 | %JSS_INVENTORY_NAME%
11 |
12 |
13 | Application Version
14 | 1
15 | and
16 | is not
17 | %VERSION%
18 |
19 |
20 | Computer Group
21 | 2
22 | and
23 | member of
24 | Testing
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Adobe/UninstallPolicyTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Uninstall Latest %PROD_NAME%
4 | true
5 | Ongoing
6 |
7 | %POLICY_CATEGORY%
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | true
21 | Uninstall %VERSION%
22 | %SELF_SERVICE_DESCRIPTION%
23 |
24 |
25 | true
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Adobe/UninstallSmartGroupTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 | %group_name%
3 | true
4 |
5 |
6 | Application Title
7 | 0
8 | and
9 | is
10 | %JSS_INVENTORY_NAME%
11 |
12 |
13 | Application Version
14 | 1
15 | and
16 | is
17 | %VERSION%
18 |
19 |
20 | Computer Group
21 | 2
22 | and
23 | member of
24 | Testing
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Adobe/examples/AcrobatDC17.jss.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Identifier
6 | local.jss.Adobe.AcrobatDC
7 | Input
8 |
9 | NAME
10 | AdobeAcrobatDC
11 | ORG_NAME
12 | ADMIN_PLEASE_CHANGE
13 | ccpinfo
14 |
15 | matchOSLanguage
16 |
17 | rumEnabled
18 |
19 | updatesEnabled
20 |
21 | appsPanelEnabled
22 |
23 | adminPrivilegesEnabled
24 |
25 | IncludeUpdates
26 |
27 | is64Bit
28 |
29 | organizationName
30 | ADMIN_PLEASE_CHANGE
31 | customerType
32 | team
33 | Language
34 | en_US
35 | Products
36 |
37 |
38 | sapCode
39 | APRO
40 | baseVersion
41 |
42 | version
43 | 15.0
44 |
45 |
46 |
47 |
48 | parse_proxy_xml
49 |
50 | fetch_description
51 |
52 | fetch_icon
53 |
54 |
55 | CATEGORY
56 | Productivity
57 | GROUP_NAME
58 | %NAME%-update-smart
59 | GROUP_TEMPLATE
60 | AcrobatDCSmartGroupTemplate.xml
61 | POLICY_CATEGORY
62 | Testing
63 | POLICY_TEMPLATE
64 | PolicyTemplate.xml
65 |
66 | ParentRecipe
67 | com.github.mosen.jss.Adobe.CreativeCloudApp
68 |
69 |
70 |
--------------------------------------------------------------------------------
/Adobe/examples/AcrobatDC17.munki.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Identifier
6 | local.munki.Adobe.AcrobatDC
7 | Input
8 |
9 | NAME
10 | AdobeAcrobatDC
11 | ccpinfo
12 |
13 | matchOSLanguage
14 |
15 | rumEnabled
16 |
17 | updatesEnabled
18 |
19 | appsPanelEnabled
20 |
21 | adminPrivilegesEnabled
22 |
23 | organizationName
24 | ADMIN_PLEASE_CHANGE
25 | customerType
26 | team
27 | Language
28 | en_US
29 | Products
30 |
31 |
32 | sapCode
33 | APRO
34 | baseVersion
35 |
36 | version
37 | 17.0
38 |
39 |
40 |
41 | MUNKI_REPO_SUBDIR
42 | apps/Adobe
43 | pkginfo
44 |
45 | blocking_applications
46 |
47 | Adobe Acrobat
48 | Acrobat Distiller
49 |
50 | catalogs
51 |
52 | testing
53 |
54 | name
55 | %NAME%
56 | developer
57 | Adobe
58 | unattended_install
59 |
60 |
61 |
62 | ParentRecipe
63 | com.github.mosen.munki.Adobe.CreativeCloudApp
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Adobe/examples/AcrobatDCSmartGroupTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 | %GROUP_NAME%
3 | true
4 |
5 |
6 | Application Title
7 | 0
8 | and
9 | is
10 | Adobe Acrobat.app
11 |
12 |
13 | Application Version
14 | 1
15 | and
16 | like
17 | 15.
18 |
19 |
20 | Application Version
21 | 2
22 | and
23 | is not
24 | %proxy_version%
25 |
26 |
27 | Computer Group
28 | 3
29 | and
30 | member of
31 | Testing
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Adobe/examples/AdobeAuditionCC2017.jss.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Identifier
6 | local.jss.AdobeAuditionCC2017
7 | Input
8 |
9 |
10 | NAME
11 | AdobeAuditionCC2017
12 | ccpinfo
13 |
14 | matchOSLanguage
15 |
16 | rumEnabled
17 |
18 | updatesEnabled
19 |
20 | appsPanelEnabled
21 |
22 | adminPrivilegesEnabled
23 |
24 | organizationName
25 | ADMIN_PLEASE_CHANGE
26 | customerType
27 | team
28 | Language
29 | en_US
30 | Products
31 |
32 |
33 | sapCode
34 | AUDT
35 | baseVersion
36 | 10.0.0
37 | version
38 | latest
39 |
40 |
41 |
42 |
43 | fetch_description
44 |
45 | fetch_icon
46 |
47 |
48 |
49 | CATEGORY
50 | Sound
51 | GROUP_NAME
52 | %NAME%-update-smart
53 | GROUP_TEMPLATE
54 | SmartGroupTemplate.xml
55 | POLICY_CATEGORY
56 | Testing
57 | POLICY_TEMPLATE
58 | PolicyTemplate.xml
59 |
60 | ParentRecipe
61 | com.github.mosen.jss.Adobe.CreativeCloudApp
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Adobe/examples/AdobePhotoshopCC2017.munki.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Identifier
6 | local.munki.AdobePhotoshopCC2017
7 | Input
8 |
9 | MUNKI_REPO_SUBDIR
10 | apps/Adobe/CC2017
11 | NAME
12 | AdobePhotoshopCC2017
13 | ccpinfo
14 |
15 | matchOSLanguage
16 |
17 | rumEnabled
18 |
19 | updatesEnabled
20 |
21 | appsPanelEnabled
22 |
23 | adminPrivilegesEnabled
24 |
25 | organizationName
26 | ADMIN_PLEASE_CHANGE
27 | customerType
28 | team
29 | Language
30 | en_US
31 | Products
32 |
33 |
34 | sapCode
35 | PHSP
36 | baseVersion
37 | 18.0
38 | version
39 | latest
40 |
41 |
42 |
43 | pkginfo
44 |
45 | blocking_applications
46 |
47 |
48 | Creative Cloud
49 | Adobe Application Manager
50 | AAM Updates Notifier
51 | Adobe Application Manager (Updater)
52 |
53 | Adobe Photoshop CC 2017
54 |
55 | catalogs
56 |
57 | testing
58 |
59 |
60 |
61 | ParentRecipe
62 | com.github.mosen.munki.Adobe.CreativeCloudApp
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Adobe/examples/AdobePhotoshopCC2017.pkg.recipe:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Identifier
6 | local.pkg.AdobePhotoshopCC2017
7 | Input
8 |
9 | NAME
10 | AdobePhotoshopCC2017
11 | ccpinfo
12 |
13 | matchOSLanguage
14 |
15 | rumEnabled
16 |
17 | updatesEnabled
18 |
19 | appsPanelEnabled
20 |
21 | adminPrivilegesEnabled
22 |
23 | organizationName
24 | ADMIN_PLEASE_CHANGE
25 | customerType
26 | enterprise
27 | Language
28 | en_US
29 | Products
30 |
31 |
32 | sapCode
33 | PHSP
34 | baseVersion
35 | 18.0
36 | version
37 | latest
38 |
39 |
40 |
41 |
42 | ParentRecipe
43 | com.github.mosen.pkg.Adobe.CreativeCloudApp
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Adobe/examples/PolicyTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Install Latest %PROD_NAME%
4 | true
5 | Ongoing
6 |
7 | %POLICY_CATEGORY%
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | true
21 | Install %VERSION%
22 | %release_notes%
23 |
24 |
25 | true
26 |
27 |
--------------------------------------------------------------------------------
/Adobe/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | This directory contains some example overrides. Since it's possible these may drift over time, these should be considered only as illustrative - creating overrides should always be done using `autopkg make-override --name `
4 |
5 | ### Known issues in JSS recipes
6 |
7 | - No SS Icons
8 | - No SS Description
9 | - Camera Raw Version Smart Group does not match EA version.
10 | - Acrobat DC Smart Group Version does not match Installed Application version.
11 | - Version string comparison might end up scoping computers for a downgrade if two package smartgroups exist.
12 | - Error in local.jss.Adobe.IllustratorCC: Processor: JSSImporter: Error: Central directory offset would require ZIP64 extensions
13 |
--------------------------------------------------------------------------------
/Adobe/examples/SmartGroupTemplate.xml:
--------------------------------------------------------------------------------
1 |
2 | %group_name%
3 | true
4 |
5 |
6 | Application Title
7 | 0
8 | and
9 | is
10 | %JSS_INVENTORY_NAME%
11 |
12 |
13 | Application Version
14 | 1
15 | and
16 | is not
17 | %VERSION%
18 |
19 |
20 | Computer Group
21 | 2
22 | and
23 | member of
24 | Testing
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ccp-recipes
2 |
3 | AutoPkg recipes for Creative Cloud Packager workflows.
4 | This repo has now been moved to [autopkg/adobe-ccp-recipes](https://github.com/autopkg/adobe-ccp-recipes),
5 | so any further development will happen there.
6 |
7 | Recipe identifiers have not changed, so if you were already tracking this repo with Git,
8 | one simple way to migrate your repo to the new remote location would be to go to your AutoPkg
9 | recipe repo dir (most likely `~/Library/AutoPkg/RecipeRepos/com.github.mosen.ccp-recipes`) and modify
10 | the `.git/config` to use the new repo location of https://github.com/autopkg/adobe-ccp-recipes.
11 |
12 | ## New behaviour with CCP v1.14.0.97 (March 2018)
13 |
14 | In v1.14.0.97 the ability to specify a custom package by its exact version was broken (either intentionally
15 | or unintentionally) by Adobe. The only remaining options for us are the base version or "latest" version of
16 | the product.
17 |
18 | The following changes were made as a result:
19 |
20 | - If you specify version "latest", it will automatically include any updates to the main product and bundled
21 | products at the time you generate the package. This means that recipes are non-deterministic.
22 | - If you don't specify a version you get the base version only.
23 | - You may use "IncludeUpdates" in place of specifying a version to mean "include all updates at the time of
24 | packaging", this is the same as "latest".
25 |
26 |
--------------------------------------------------------------------------------
/list_ccp_feed:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2016 Mosen/Tim Sutton
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | import os
18 | import sys
19 | import json
20 | import unicodedata
21 | import urllib2
22 | from urllib import urlencode
23 |
24 | CCM_URL = 'https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v4/products/all'
25 | BASE_URL = 'https://prod-rel-ffc.oobesaas.adobe.com/adobe-ffc-external/aamee/v2/products/all'
26 |
27 | def add_product(products, product):
28 | if product['id'] not in products:
29 | products[product['id']] = []
30 |
31 | products[product['id']].append(product)
32 |
33 |
34 | def feed_url(channels, platforms):
35 | """Build the GET query parameters for the product feed."""
36 | params = [
37 | ('payload', 'true'),
38 | ('productType', 'Desktop'),
39 | ('_type', 'json')
40 | ]
41 | for ch in channels:
42 | params.append(('channel', ch))
43 |
44 | for pl in platforms:
45 | params.append(('platform', pl))
46 |
47 | return BASE_URL + '?' + urlencode(params)
48 |
49 | def fetch(channels, platforms):
50 | """Fetch the feed contents."""
51 | url = feed_url(channels, platforms)
52 | print('Fetching from feed URL: {}'.format(url))
53 |
54 | req = urllib2.Request(url, headers={
55 | 'User-Agent': 'Creative Cloud',
56 | 'x-adobe-app-id': 'AUSST_4_0',
57 | })
58 | data = json.loads(urllib2.urlopen(req).read())
59 |
60 | return data
61 |
62 | def dump(channels, platforms):
63 | """Save feed contents to feed.json file"""
64 | url = feed_url(channels, platforms)
65 | print('Fetching from feed URL: {}'.format(url))
66 |
67 | req = urllib2.Request(url, headers={
68 | 'User-Agent': 'Creative Cloud',
69 | 'x-adobe-app-id': 'AUSST_4_0',
70 | })
71 | data = urllib2.urlopen(req).read()
72 | with open(os.path.join(os.path.dirname(__file__), 'feed.json'), 'w+') as feed_fd:
73 | feed_fd.write(data)
74 | print('Wrote output to feed.json')
75 |
76 |
77 | if __name__ == "__main__":
78 | if len(sys.argv) > 1 and sys.argv[1] == 'dump':
79 | dump(['ccp_hd_2', 'sti'], ['osx10', 'osx10-64'])
80 | else:
81 | data = fetch(['ccp_hd_2', 'sti'], ['osx10', 'osx10-64'])
82 | products = {}
83 | for channel in data['channel']:
84 | for product in channel['products']['product']:
85 | add_product(products, product)
86 |
87 | for sapcode, productVersions in products.iteritems():
88 | print("SAP Code: {}".format(sapcode))
89 |
90 | for product in productVersions:
91 | base_version = product['platforms']['platform'][0]['languageSet'][0].get('baseVersion')
92 | if not base_version:
93 | base_version = "N/A"
94 |
95 | name = unicodedata.normalize("NFKD", product['displayName'])
96 | print("\t{0: <60}\tBaseVersion: {1: <14}\tVersion: {2: <14}".format(
97 | name,
98 | base_version,
99 | product['version']
100 | ))
101 | print("")
102 |
--------------------------------------------------------------------------------
/whats_my_org.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Find out your Adobe organization name by scraping the PDApp log
3 |
4 | output=$(mktemp)
5 | grep 'OrgDetails return status is (0) server-response' ~/Library/Logs/PDApp.log | tail -n 1 | cut -d\( -f3 | cut -d\) -f1 |plutil -convert xml1 - -o - > "${output}"
6 | /usr/libexec/PlistBuddy -c 'Print 0:orgName' "${output}"
7 |
--------------------------------------------------------------------------------