환경

  • Java
  • IntelliJ


환경 설정

Spring Test 준비하기

Rest Assured Gradle 설정

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.2'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'me.twpower'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '21'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testImplementation 'io.rest-assured:rest-assured:5.4.0' // 추가됨
}

tasks.named('test') {
	useJUnitPlatform()
}


코드

  • given(): 요청하기 전에 필요한 헤더나 파라미터와 같은 부분을 세팅
  • when(): 실제로 요청하기위해 URI나 Method를 입력
  • then(): 검증하기 위한 부분
  • header, pathParam, queryParam, body, log 그리고 extract 메소드 사용 방법도 아래에 추가
package me.twpower.restassuredpractice;

import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.equalTo;

@SpringBootTest
public class RestAssuredPracticeTest {

    @BeforeAll
    static void beforeAll(){
        // Setting BaseURI
        RestAssured.baseURI = "http://echo.jsontest.com/";
    }

    @Test
    void restAssuredPracticeTest() {
        // JSON Example
        // Request Method: GET
        // Call http://echo.jsontest.com/key1/value1/key2/value2/key3/3?queryParameterKey=queryParameterValue
        /*
        {
            "key1": "value1",
            "key2": "value2",
            "key3": "3"
        }
        */

        // given(): Start building the request part of the test io.restassured.specification.
        // when(): Start building the DSL expression by sending a request without any parameters or headers etc.
        // then(): Returns a validatable response that's lets you validate the response.

        // given() and when() returns RequestSpecification object
        ExtractableResponse<Response> extractableResponse = given().log().all().
            header("Content-Type", "application/json"). // Specify the headers that'll be sent with the request.
            pathParam("pathParameter", 3). // Specify a path parameter.
            queryParam("queryParameterKey", "queryParameterValue"). // Specify a query parameter that'll be sent with the request.
            //body(). // Specify request body.
        when().
            get("/key1/value1/key2/value2/key3/{pathParameter}").
        then().
            body("key1", equalTo("value1")).
            extract();

        Assertions.assertEquals(200, extractableResponse.statusCode());
        Assertions.assertEquals("value1", extractableResponse.jsonPath().getString("key1"));
    }
}


결과

  • 성공
  • 실패
java.lang.AssertionError: 1 expectation failed.
JSON path key1 doesn't match.
Expected: value2
  Actual: value1


참고자료


Environment and Prerequisite

  • Java
  • IntelliJ


Setting

Prepare Spring Test

Setting Rest Assured in Gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.2'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'me.twpower'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '21'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testImplementation 'io.rest-assured:rest-assured:5.4.0' // Added
}

tasks.named('test') {
	useJUnitPlatform()
}


Code

  • given(): Set up necessary components such as headers or parameters before making a request
  • when(): Input URI or method for the actual request
  • then(): Define verification steps
  • Add the usage methods for header, pathParam, queryParam, body, log and extract below.
package me.twpower.restassuredpractice;

import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.equalTo;

@SpringBootTest
public class RestAssuredPracticeTest {

    @BeforeAll
    static void beforeAll(){
        // Setting BaseURI
        RestAssured.baseURI = "http://echo.jsontest.com/";
    }

    @Test
    void restAssuredPracticeTest() {
        // JSON Example
        // Request Method: GET
        // Call http://echo.jsontest.com/key1/value1/key2/value2/key3/3?queryParameterKey=queryParameterValue
        /*
        {
            "key1": "value1",
            "key2": "value2",
            "key3": "3"
        }
        */

        // given(): Start building the request part of the test io.restassured.specification.
        // when(): Start building the DSL expression by sending a request without any parameters or headers etc.
        // then(): Returns a validatable response that's lets you validate the response.

        // given() and when() returns RequestSpecification object
        ExtractableResponse<Response> extractableResponse = given().log().all().
            header("Content-Type", "application/json"). // Specify the headers that'll be sent with the request.
            pathParam("pathParameter", 3). // Specify a path parameter.
            queryParam("queryParameterKey", "queryParameterValue"). // Specify a query parameter that'll be sent with the request.
            //body(). // Specify request body.
        when().
            get("/key1/value1/key2/value2/key3/{pathParameter}").
        then().
            body("key1", equalTo("value1")).
            extract();

        Assertions.assertEquals(200, extractableResponse.statusCode());
        Assertions.assertEquals("value1", extractableResponse.jsonPath().getString("key1"));
    }
}


Result

  • Success
  • Fail
java.lang.AssertionError: 1 expectation failed.
JSON path key1 doesn't match.
Expected: value2
  Actual: value1


Reference


환경

  • AWS


배경

  • AWS KMS Key Policy에 AWS IAM Role ARN을 넣었는데 고유 식별자(Unique Identifier)로 바뀌는 현상이 발생
  • 처음에는 고유 식별자(Unique Identifier)인줄 모르고 이상한 해시 값 같은 문자열로 바뀌어 있어서 AWS에 문의함
"Principal": {
  "AWS": [
    "arn:aws:iam::111122223333:role/role-name",
    "AIDACKCEVSQ6C2EXAMPLE",
    "AROADBQP57FF2AEXAMPLE"
  }


AWS IAM ARN Unique Identifier

AWS 공식 문서에 따르면 아래와 같이 고유 식별자(Unique Identifier)가 생성된다고 한다.

“IAM에서 사용자, 사용자 그룹, 역할, 정책, 인스턴스 프로파일 또는 서버 인증서를 생성할 때, 각 리소스에 고유 ID를 할당합니다.”

그래서 위에 나오는 문자열들도 다 고유 식별자(Unique Identifier)로 볼 수 있다. 위에 있는 예시도 공식 문서에 있는 예시이다.


Policy에서 ARN이 고유 식별자(Unique Identifier)로 바뀌는 이유

AWS에 문의해보니 Policy에 IAM ARN을 넣었을 때 고유 식별자(Unique Identifier)로 바뀌는 이유는 해당 ARN이 삭제되었기 때문이라고 한다. 새로 ARN을 동일한 이름으로 만들더라도 고유 식별자(Unique Identifier)는 다르기 때문에 삭제하고 추가하는게 맞다고 전달 받았다. 결국 저렇게 Policy에 ARN이 고유 식별자(Unique Identifier)로 바뀌어버리면 어차피 삭제된 IAM ARN이기 때문에 보이면 삭제하는게 맞는거 같다.


참고자료


Environment and Prerequisite

  • AWS


Background

  • AWS IAM Role ARN is automatically changed to unique identifier in AWS KMS Key Policy at a certain moment
  • At first I didn’t know it was a unique identifier and it was changed to like a hash string. So I asked to AWS
"Principal": {
  "AWS": [
    "arn:aws:iam::111122223333:role/role-name",
    "AIDACKCEVSQ6C2EXAMPLE",
    "AROADBQP57FF2AEXAMPLE"
  }


AWS IAM ARN Unique Identifier

According to AWS official document, there is an unique indentifier.

“When IAM creates a user, user group, role, policy, instance profile, or server certificate, it assigns a unique ID to each resource.”

So we can consider above all strings as unique identifiers. Above example is on official document.


Reason of AWS IAM ARN changed to unique identifier in policy

The reason of why AWS IAM ARN changed to unique identifier in policy is because it is deleted. Even though create with same name, its ARN will have a new unique identifier so it is recommended to erase and add new ARN. In the end, if ARN changes to a unique identifier like that in above policy it has been deleted anyway. So it seems right to delete it if it exist.


Reference

ssh를 사용해 crontab을 수정하는 방법에 대한 정리


환경

  • Linux
  • SSH(OpenSSH)
  • crontab


방법

crontab

crontab [-u user] file
crontab [-u user] { -l | -r [-f] |	-e }
  • 표준입력(stdin)을 주거나 crontab 명령어 다음에 파일명을 넘겨주면 해당 표준입력(stdin)이나 파일로 현재의 crontab 내용을 덮어쓴다.
  • ssh로 수정시 crontab -e를 사용할 수 없기 때문에 위의 방법을 사용한다.
  • 문서를 참고하면 “The first form of this command is used to install a new crontab from some named file or standard input if the pseudo-filename ‘-‘ is given”이라고 나와있다.


예시

시나리오

  • 현재 컴퓨터에서 서버에 있는 crontab 내용을 수정하는 시나리오

예시

  • 서버에 있는 crontab 내용 확인
# Check remote crontab
ssh twpower@157.230.234.230 "crontab -l"
0 10 17 * * /home/twpower/simple-script.sh
40 7 * * 1 python /home/twpower/run.py
  • crontab의 내용을 /tmp에 복사후에 수정하고 해당 내용을 crontab에 적용
# Copy current crontab content to temporary file
ssh twpower@157.230.234.230 "crontab -l > /tmp/tmp-crontab-content"

# Add crontab content to temporary file
# Not only addition but also deletion and modification are possible
# Many variations are possible like using sed, tee or cat
ssh twpower@157.230.234.230 "echo '* * * * 1 python /home/twpower/run.py' >> /tmp/tmp-crontab-content"

# Apply new crontab using temporary file
ssh twpower@157.230.234.230 "crontab /tmp/tmp-crontab-content"

# Remove temporary file
ssh twpower@157.230.234.230 "rm -rf /tmp/tmp-crontab-content"
  • 결과 확인
# Check remote crontab
ssh twpower@157.230.234.230 "crontab -l"
0 10 17 * * /home/twpower/simple-script.sh
40 7 * * 1 python /home/twpower/run.py
* * * * 1 python /home/twpower/run.py

스크립트

  • 아래처럼 스크립트로 만들수도 있다.
#!/bin/bash

# Copy current crontab content to temporary file
ssh twpower@157.230.234.230 "crontab -l > /tmp/tmp-crontab-content"

# Add crontab content to temporary file
# Not only addition but also deletion and modification are possible
# Many variations are possible like using sed, tee or cat
ssh twpower@157.230.234.230 "echo '* * * * 1 python /home/twpower/run.py' >> /tmp/tmp-crontab-content"

# Apply new crontab using temporary file
ssh twpower@157.230.234.230 "crontab /tmp/tmp-crontab-content"

# Remove temporary file
ssh twpower@157.230.234.230 "rm -rf /tmp/tmp-crontab-content"


참고자료