How to import Thingworx Extension using curl

개요

Thingworx에서 Extension을 Composer UI가 아니라 curl로 수행해야하는 필요성은 Jenkins를 사용한 Thingworx 배포를 완전 자동화 해야하는 요구사항에서 비롯되었다.

Thingworx의 배포

Thingworx는 “안전한 배포”에 대한 방법이 한정적이다.(일단 내가 경험한 프로젝트에서는 그래왔다..)
대략적으로 검색해보니 PTC의 클라우드를 쓰면 그쪽의 전문가들이 뭔가를 더 해주는 모양이지만..
일단 내가 경험했던 프로젝트들은 전부

  • 클라우드가 아닌 온프로미스로 서버를 구성한 상태였고,
  • 개발서버와 운영서버로 나뉘어 있었으며,
  • 개발 > 운영 포팅 시 Thingworx Platform 범위에서는 Export 지원되는 각종 Entitity들을 일괄/개별 Export 하여 타겟 서버에서 Composer UI Importer를 이용하여 Import 하는 방식으로 진행되고,
  • Extension들은 Thingworx에서 추출하지 않고 별도의 보유 Extension들을 타겟 서버 대상으로 버전을 맞춰 빌드한 후에 타겟 서버에서 Composer UI를 이용하여 Import 하는 방식을 사용했다.

정리하면 Entity와 Extension을 구분해서 포팅 및 배포하고 Extension 배포 후 필요하면(Java Extension의 버전 업이 포함되어 있었다면) 서버 재시작 까지가 기본적인 배포 절차이다.

기존 배포 방식에서의 주의사항

Thingworx의 Entity들은 Import 순서가 결과에 영향을 줄 수 있다.
Thingworx Exporter로 뽑아낸 source control 형태의 파일을 그대로 Import 하는 경우는 Thingworx가 내부적으로 의존 관계를 고려해 Import하는 Entity Type의 순서를 조정해서 작업하기 때문에 문제가 없다.
하지만 Entity들을 개별적으로 Import 하는 경우 Import 순서가 문제가 될 수 있다.
예를들어, 어떤 Thing에 ValueStream이 지정되어 있었고 이 Thing과 ValueStream 둘 다 신규 Import를 해야하는 경우에 Thing을 먼저 Import하려고 하면 ValueStream을 찾을 수 없어 실패할 것이다. Import를 성공시키려면 ValueStream 먼저 수행한 후에 Thing을 Import 해야한다.

Extension들은 Java/non-Java 여부가 Import 후에 서버 재시작 여부에 영향을 준다.

Thingworx의 Importer, ExtensionPackageUploader 모듈과 REST API

Thingworx에서 일반 Entity들은 Importer 모듈을 사용해 처리되고,
Extension 들은 ExtensionPackageUploader 모듈로 처리된다.

Thingworx는 REST API 친화적인 솔루션이기 때문에 Auth를 인증할 방법만 있으면 대부분의 서비스와 모듈을 REST로 처리하는 자유로운 시도가 가능하다.
Auth는 id/pw를 암호화 및 인코딩 해서도 사용할 수 있는 여지가 있었다.(Thingworx 8.3 버전에서는 테스트를 해봤는데 최신 버전들은 확인되지 않음)
하지만 가장 정석적이고 안전한 방법은 appkey Entity를 만들어 사용하는 것이다.

curl을 사용한 Extension Import 방법

잘 빌드된 Extension이 있다면 curl을 통해 Extension을 Import 할 수 있다.
(나는 Ubuntu의 Jenkins를 통해 Extension을 Import해야하는 니즈가 있어서 curl로 작업했지만 헤더와 페이로드만 잘 세팅해주면 Postman같은 API 테스팅 툴로도 시도할 수 있다.)

우선 인증을 위한 appkey를 생성한다.
appkey의 기본 유효기간이 짧기 때문에, 적절한 유효기간을 설정하고 적절한 권한을 가진 유저를 할당하여 appkey를 생성하자.

그 후 shell에서 아래와 같이 호출하여 Extension을 Import 할 수 있다.

  • Window에서 Powershell 사용
> curl.exe -s -v -w "%{http_code}" `
	-X POST "https://{Thingworx 사이트}/Thingworx/ExtensionPackageUploader?purpose=import" `
	-H "appKey: {생성한 appkey}" `
	-H "Content-Type: multipart/form-data" `
	-H "Accept: application/json" `
	-H "x-thingworx-session: true" `
	-H "X-XSRF-TOKEN: TWX-XSRF-TOKEN-VALUE" `
	-F "upload=@{Extension 파일 경로}"
  • Linux에서 실행
curl -s -w "%{http_code}" 
-X POST "https://{Thingworx 사이트}/Thingworx/ExtensionPackageUploader?purpose=import" \ 
-H "appKey: {생성한 appkey}"\ 
-H "Content-Type: multipart/form-data"\ 
-H "Accept: application/json"\ 
-H "x-thingworx-session: true"\ 
-H "X-XSRF-TOKEN: TWX-XSRF-TOKEN-VALUE"\ 
-F "upload=@{Extension 파일 경로}"

이대로 실행하면 Extension 자체는 잘 Import 되고, Java Extension인 경우에는 서버 재시작 이후에 정상적으로 반영된 것을 확인할 수 있을 것이다.

Extension Import 결과 확인 시 주의사항

Java Extension의 Import 시에는 Response code를 가지고 Import 성공 여부를 판단할 수 없음을 명심하자.

사실 이 항목 때문에 이 정보글을 쓰게 된 것이다.

위에서 한번 언급했듯이, Extension들은 Java/non-Java 여부가 Import 후에 서버 재시작 여부에 영향을 준다.
non-Java Extension의 경우에는 서버 재시작 없이 반영되지만 Java Extension은 Import하면 일단 특정 위치의 Storage에 저장되면서 이 Extension을 서버 재시작 시 올리기 위한 큐에 등록된다.
이 과정은 서버를 재시작 해야지만 올라가기 때문에 서버 재시작이 필수적이다.
그렇기 때문에 Java Extension 요청의 결과는 Extension Import 성공 여부와 동일시 해서는 안되며, 오로지 Import 성공 여부는 서버 재시작 이후에 Application.log를 확인해야 알 수 있다.

그래서인지 non-Java Extension의 경우에는 Import 성공 시 무조건 Response가 200 OK로 식별 가능하게 돌아오지만 Java Extension의 경우에는 Response가 406 으로 돌아오며 에러 수준이 Warning으로 분류되어있기 때문에 단순 응답 코드로 Import 여부를 판별하려고 한다면 혼동이 올 수 있다.
응답 코드로 예외처리 또는 성공 여부를 판별하고 싶은 경우에는 200과 406 코드를 전부 Import 시도 패스로 간주하도록 처리하자.

참고로 Thingworx Composer의 Extension Import UI에서도 Java Extension Import 시에 화면상에서는 온건하게 표시되지만, Respose 는 406으로 에러처럼 표시된다.

non-Java Extension의 경우에는 200 OK로 응답 받아왔기 때문에 이 부분에 대한 고려가 부족해서 Extension Import를 실패로 보고 관련 케이스를 열심히 찾았으나 Import 성공했으면서 응답만 406로 온 경우에 대한 미스테리로 한참을 원인 규명에 시간을 썼다.(Report된 케이스는 전부 Import에 실패하고, Response도 에러로 받은 경우였다.)

Thingworx Platform Help center - Common Extension Import Result Messages
위 링크에서도 Java Extension인 경우에 받게되는 메시지에 Warning이 포함되어있다고 명시되어있다.

이것으로 미루어보아 406 응답에 대한 것 또한 “문제”라고 인식하지는 않아도 될 것으로 추정된다.

마무리

누가 과연 또 Thingworx DevOps 환경을 구축하거나 REST로 모든 것을 해결하려하면서 나와 비슷한 케이스를 겪고 정보를 찾을지는 모르겠지만, 나도 오류라고 생각하고 꽤 시간을 들여 케이스를 찾고, 문제가 아니라고 결정하기 까지 많은 시간이 들었기 때문에 언젠가 누군가에게 도움이 될 것이라 생각해서 정보를 공유한다.