diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..33415f8 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# These owners will be the default owners for everything in the repo. +* @acv-auctions/Salesforce-Infrastructure \ No newline at end of file diff --git a/.pypirc b/.pypirc new file mode 100644 index 0000000..105d83d --- /dev/null +++ b/.pypirc @@ -0,0 +1,8 @@ +[distutils] +index-servers = + nexus + +[nexus] +repository: https://nexus.infra.acvauctions.com/repository/pypi-releases/ +username: +password: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cf06c94 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.7.3-alpine3.9 + +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk && apk add --no-cache gcc bash musl-dev libffi-dev \ +openssl-dev build-base git + +RUN mkdir -p /opt/sfdclib +WORKDIR /opt/sfdclib +RUN pip install --user pipenv +ENV PATH="/root/.local/bin:${PATH}" + +ARG NEXUS_USER +ENV NEXUS_USER=$NEXUS_USER + +ARG NEXUS_PASSWORD +ENV NEXUS_PASSWORD=$NEXUS_PASSWORD + +RUN pipenv lock +RUN pipenv sync --dev + +ARG VERSION +ENV APP_VERSION=$VERSION + +COPY sfdclib /opt/sfdclib/sfdclib +COPY .pypirc /root/.pypirc +COPY run.sh /opt/sfdclib/run.sh +COPY setup.py /opt/sfdclib/setup.py +COPY README.* /opt/sfdclib/ + +ENV APP_MODE=$APP_MODE +CMD ["sh", "-c", "./run.sh"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..74f3534 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,31 @@ +@Library('acv-jenkins-lib') +import com.acvJenkins.*; + +pipeline { + agent { + kubernetes { + defaultContainer 'docker' + yaml new GlobalVars().runnerPod() + } + } + environment { + NEXUS_CREDS = credentials('NEXUS_CREDS') + NEXUS_USER = "${NEXUS_CREDS_USR}" + NEXUS_PASSWORD = "${NEXUS_CREDS_PSW}" + } + stages { + stage('Publish') { + when { + expression { env.BRANCH_NAME.endsWith('-acv') } + } + steps { + sh 'make deploy' + } + } + } + post { + always { + sh 'make --ignore-errors stop-ci' + } + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20a5883 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +CONTAINER_NAME := acv-sfdc-ci +VERSION := $(shell git fetch --tags && git describe --tags) + +.PHONY: clean +clean: + find . -type f -name '.DS_Store' -delete -o -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete + +.PHONY: deps +deps: + pipenv install + pipenv install --dev + +build: clean + git submodule update --init --recursive + @docker build \ + --build-arg NEXUS_USER=${NEXUS_USER} \ + --build-arg NEXUS_PASSWORD=${NEXUS_PASSWORD} \ + --build-arg VERSION=${VERSION} \ + --tag $(CONTAINER_NAME):$(VERSION) . + +.PHONY: deploy +deploy: build + docker run \ + -e APP_MODE=deploy \ + -e NEXUS_USER=${NEXUS_USER} \ + -e NEXUS_PASSWORD=${NEXUS_PASSWORD} \ + -v /var/run/docker.sock:/var/run/docker.sock \ + $(CONTAINER_NAME):$(VERSION) + +.PHONY: stop-ci +stop-ci: + docker ps -a -q --filter ancestor=$(CONTAINER_NAME):$(VERSION) | xargs docker rm -f + docker ps -a -q --filter ancestor=mysql:5.7git s | xargs docker rm -f diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..829f7b5 --- /dev/null +++ b/run.sh @@ -0,0 +1,13 @@ +[ -z "$APP_MODE" ] && APP_MODE="test" + +sed -i'.old' 's/username:/username: '$(echo $NEXUS_USER)'/g' /root/.pypirc +sed -i'.old' 's/password:/password: '$(echo $NEXUS_PASSWORD)'/g' /root/.pypirc +sed -i'.old' 's/\%40/\@/g' /root/.pypirc + +if [ $APP_MODE = "deploy" ]; then + cat /root/.pypirc + python setup.py sdist upload -r nexus + EXIT_CODE=$? +fi + +exit $EXIT_CODE \ No newline at end of file diff --git a/setup.py b/setup.py index 176654a..29007e6 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ from setuptools import setup setup( - name='sfdclib', - version='0.2.26', + name='sfdclib-acv', + version='0.2.26.12', author='Andrey Shevtsov', author_email='ashevtsov@rbauction.com', packages=['sfdclib'], diff --git a/sfdclib/rest.py b/sfdclib/rest.py index 801cb65..6e1545f 100644 --- a/sfdclib/rest.py +++ b/sfdclib/rest.py @@ -10,12 +10,17 @@ class SfdcRestApi: """ Class to work with Salesforce REST API """ _API_BASE_URI = "/services/data/v{version}" _SOQL_QUERY_URI = "/query/?{query}" + _API_APEXREST_URI = "/services/apexrest" def __init__(self, session): if not session.is_connected(): raise Exception("Session must be connected prior to instantiating this class") self._session = session + def _get_apexrest_api_uri(self): + """Returns APEXREST API base URI without the version for this connection""" + return self._API_APEXREST_URI + def _get_api_uri(self): """ Returns REST API base URI for this connection """ return self._API_BASE_URI.format(**{'version': self._session.get_api_version()}) @@ -46,6 +51,12 @@ def post(self, uri, data): response = self._session.post(url, headers=self._get_headers(), json=data) return self._parse_get_post_response(response) + def patch(self, uri, data): + """" HTTP PATCH request""" + url = self._session.construct_url(self._get_api_uri() + uri) + response = self._session.patch(url, headers=self._get_headers(), json=data) + return self._parse_get_post_response(response) + def delete(self, uri): """ HTTP DELETE request """ try: diff --git a/sfdclib/session.py b/sfdclib/session.py index f84a5de..4755d08 100644 --- a/sfdclib/session.py +++ b/sfdclib/session.py @@ -5,7 +5,7 @@ class SfdcSession(Session): - _DEFAULT_API_VERSION = "37.0" + _DEFAULT_API_VERSION = "53.0" _LOGIN_URL = "https://{instance}.salesforce.com" _SOAP_API_BASE_URI = "/services/Soap/c/{version}" _XML_NAMESPACES = { diff --git a/sfdclib/tooling.py b/sfdclib/tooling.py index 45ad9c8..69fc8ea 100644 --- a/sfdclib/tooling.py +++ b/sfdclib/tooling.py @@ -60,6 +60,17 @@ def post(self, uri, data): response = self._session.post(url, headers=self._get_headers(), data=data) return self._parse_get_post_response(response) + def patch(self, uri, data): + ''' HTTP PATCH request ''' + url = self._session.construct_url(self._get_tooling_api_uri() + uri) + response = self._session.patch(url, headers=self._get_headers(), json=data) + # Since we can't rely on Salesforce to give us a good JSON response on these PATCH requests, just wrap in + # a try except and hope for the best... + try: + return self._parse_get_post_response(response) + except Exception: + return response + def delete(self, uri): ''' HTTP DELETE request ''' try: