오픈소스 기여! 나도 할수 있다⭐️

여러분은 우리가 매일 사용하는 다양한 소프트웨어가 어떻게 만들어지고 발전하는지 궁금해 본 적 있으신가요?
그 답 중 하나는 바로 '오픈소스'에 있습니다. 오픈소스는 전 세계의 개발자들이 힘을 합쳐 더 나은 소프트웨어를 만들어가는
협업의 장입니다. 이 공간에서는 누구나 자신의 아이디어를 더하고, 문제를 해결하며, 기술을 발전시킬 수 있습니다.

오픈소스에 기여하는 것은 마치 전 세계 사람들과 함께 퍼즐을 맞추는 것과 같습니다. 각자가 가진 작은 조각들이 모여 큰 그림을 완성해 나가는 과정이죠. 처음에는 어디서부터 시작해야 할지 막막할 수 있지만, 조금씩 참여하다 보면 어느새 여러분도 중요한 일원으로 활약하고 있을 거예요.

 

하지만 오픈소스 기여를 처음 시작하는 사람들에게는 어디서부터 시작해야 할지 막막할 수 있습니다. 어떤 프로젝트를 선택해야 할지, 어떻게 기여할 수 있을지, 기여 과정에서 어떤 도구와 방법을 사용해야 할지에 대한 명확한 지침이 필요합니다. 이 블로그에서는 오픈소스 기여의 기초부터 시작하여, 단계별로 기여 방법에 대해 알려드리도록 할게요😁

 

오픈소스 선정

처음에 오픈소스를 선정하는 것은 어려운 작업입니다. 평소에 자주 사용하는 오픈소스 일 지라도 어떤 이슈가 할당되어 있으며, 오픈소스의 플로우를 대략적으로 파악하고 있어야 이슈를 해결할 수 있기에 오픈소스를 선정하는 것은 어려운데요, 몇가지 팁이 있습니다!

라벨을 이용하자!

각각의 오픈소스들에는 issue에 라벨이 할당되어 있습니다.

spring-security issues

issue 제목 옆에 색상으로 구분되어 있는 것이 라벨인데요! 해당 이슈에 할당된 라벨을 살펴보면 적절한 이슈를 찾을 수 있습니다.

특히 오픈소스 기여의 초보자일 경우에는 first-time-only와 같은 이슈를 해결하면 좋은데요!

type이 bug인 issue나 enhancement의 경우에는 비교적 쉬운 로직으로 오픈소스를 해결할 수도 있습니다.

깃허브 고급 검색을 사용하자!

github 고급검색

깃허브에는 고급 검색이 있는 것을 알고 계셨나요?

깃허브에서 spring을 검색한 후의 왼쪽 nav바 하단에 Advanced search가 있는 것을 볼 수 있는데요 해당 내용을 살펴보면

  • language: 선택한 언어로 개발
  • 팔로워의 수나 스타 수: ex) > 5000
  • 라벨: ex) first-time-only

과 같이 고급 검색을 이용하면 조금 더 이슈를 편하게 확인할 수 있습니다!

오픈소스 기여하기

이제 기여할 이슈를 찾았다면 해당 레포를 포크한 후, 각자의 레포에서 개발하시면 되는데요!

이슈를 해결하고 커밋을 남겨 pull request 하는 데에 정답은 없지만 제가 지키는 몇가지가 있는데요

커밋은 하나만 남기자!

개발을 하다보면 커밋 내용이 많아질 수도 있고, pr을 보내 메인 테이너가 리뷰를 주어 몇몇 수정사항이 생기는데요, 해당 이슈에 대해 커밋을 여러개 남기는 것은 커밋 이력을 확인하기 어렵습니다ㅜㅜ 따라서 저는 rebase와 squash를 사용하여, 하나의 이슈에 대해서는 하나의 커밋만 남기는 편입니다!

커밋 메시지를 자세히 그리고 issue 번호까지!

저 같은 경우에는 커밋 메시지를 한 줄로 적는 경우가 많았습니다(git commit -m "<commit message>"

하지만 기존 프로젝트와 달리 오픈소스의 경우는 많은 개발자의 작업으로 이루어지는 것이므로 커밋 메시지를 자세히 작성하는 것이 좋다고 생각합니다.

armeria 커밋 메시지

제가 해결했던 armeria 이슈 해결 커밋 내역인데요, git commit을 통해 이슈 내용과, 수정사항들을 자세히 작성했습니다!

또 커밋에 #이후에 이슈 번호를 작성하면 링크가 자동적으로 걸려 해당 이슈로 이동도 가능합니다!

 

마무리

이렇게 오픈소스를 기여하는 방법에 대해 알아봤는데요. 사실 전세계에 있는 사람들이 사용하는 오픈소스에 기여하는 것은 조금 두렵고 겁나는 일인 것은 사실입니다. 그래서 몇가지 도움을 주시는 분들이 있는데 소개해드리자면

과학기술통신부 주최의 open up에서 진행하는 컨트리뷰션 아카데미가 있습니다. 해당 리드 개발자분들이 이끌어주어 참여 기간동안 해당 오픈소스를 분석하고, 이슈를 해결하는 프로젝트가 있습니다. 체험형/입문형/참여형으로 나누어지며 신청기간은 3~5월 사이에 이루어지니 참고하시기 바랍니다ㅎㅎ

그리고 저도 참여했던 오픈소스 멘토링이 있습니다. 리드를 해주시는 인제님과 같이 이슈를 같이 분석하고 해결하며 pr까지 정해진 날짜에 짧은 기간동안 적극적으로 도와주십니다. 구글 밋을 통해 진행하고 굉장히 친절하고 깊이 있게 알려주시니 초보자분들도 무리없이 지원하실 수 있을 것 같습니다😁

 

이제 오픈소스 기여의 매력과 방법에 대해 어느 정도 감이 오셨나요? 여러분의 작은 기여가 모여 큰 변화를 만들어낼 수 있습니다. 처음에는 작은 일부터 시작해보세요. 코드 한 줄의 수정, 문서의 개선, 버그 리포팅 등 모든 기여는 소중합니다. 
이번 주말부터 오픈소스 기여에 같이 동참할까요😁?

 

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

ISSUE

final FlowJobBuilder flowJobBuilder = this.parentJobConfig.customJobBuilders()
		.get(jobName)
		.start(step1)
		.on(ExitStatus.FAILED.getExitCode())
		.fail()
		.on(ExitStatus.STOPPED.getExitCode())
		.stop()
		.next(step2)
		.on(ExitStatus.COMPLETED.getExitCode())
		.end()
		.on(ExitStatus.FAILED.getExitCode())
		.fail()
		.on(ExitStatus.STOPPED.getExitCode())
		.stop()
		.build();
  • o.s.b.core.job.flow.support.SimpleFlowMap<String, Set<StateTransition>>으로
    매개변수화된 변수 전환 Map에서 상태 전환을 가리킴.
  • o.s.b.core.job.builder.FlowJobBuilder를 사용하여 Set에 중복된 항목을 얻는 에러가 발생.

StateTransition 역할

StateTransition 클래스는 상태 전이(State Transition)를 표현하는 역할

  • 상태 저장: 특정 전이에 관련된 상태 정보를 저장.
  • 패턴 관리: 전이 조건을 정의하는 패턴을 관리.
  • 다음 상태 정의: 전이가 완료된 후 다음 상태를 정의.
  • 상태 전이 비교: 다른 상태 전이 객체와의 비교를 통해 전이의 동등성을 판단.
  • 해시 코드 제공: 상태 전이를 해시 기반 컬렉션에 저장할 수 있도록 해시 코드를 제공.
  • 불변 객체: 상태 전이 객체가 불변임을 보장하여 스레드 안전성을 높임.

해결

코드

// StateTransition.java
public final class StateTransition {
	// ... other

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		StateTransition that = (StateTransition) o;
		return Objects.equals(state, that.state) &&
				Objects.equals(pattern, that.pattern) &&
				Objects.equals(next, that.next);
	}

	@Override
	public int hashCode() {
		return Objects.hash(state, pattern, next);
	}
}

// StateTransitionTests.java
@Test
public void testEquals() {
    StateTransition A = StateTransition.createStateTransition(state, "pattern1", "next1");
    StateTransition B = StateTransition.createStateTransition(state, "pattern1", "next1");
    StateTransition C = StateTransition.createStateTransition(state, "pattern2", "next2");

    assertTrue(A.equals(B));
    assertFalse(A.equals(C));
    assertTrue(A.equals(A));
    assertFalse(A.equals(null));
}

equals 메서드

  • 두 객체가 동일한 객체인지(this == o) 확인.
  • 비교 대상이 null이 아니고 동일한 클래스인지 확인.
  • StateTransition의 필드인 state, pattern, next가 모두 같은지 비교.

hashCode 메서드

  • Objects.hash 메서드를 사용하여 상태, 패턴, 다음 상태의 해시 값을 결합하여 해시 코드를 생성.

Test code

  • equals 메서드가 다양한 조건에서 올바르게 동작하는지 확인
  • StateTransition 객체 생성
    • A, B는 동일한 필드. C는 다른 필드를 가지는 객체 생성
  • 동일한 필드 확인(assertTrue(A.equals(B))
    • A와 B는 동일한 필드를 가지므로 equals 메서드는 true를 반환.
  • 다른 필드 확인(assertFalse(A.equals(C))
    • A와 C는 패턴과 다른 필드를 가지므로 equals 메서드는 false를 반환.
  • 자기 자신 비교(assertTrue(A.equals(A))
    • A는 자기 자신이므로 equals 메서드는 true를 반환.
  • A와 null과의 비교(assertFalse(A.equals(null))
    • A는 null과 비교할 수 없으므로 equals 메서드는 false를 반환.

느낀점

  • 중복되는 객체를 확인을 어떻게 하는지 고민이였는데, 필드가 많지 않아 필드로 확인하는 로직으로 구현.
  • hashcode의 의미는 해시 기반 컬렉션에서 객체의 위치를 빠르게 찾기 위해 사용.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

ISSUE

CasAuthenticationProvider에 userDetailsChecker 멤버가 final로 선언되어 있으며, setter가 없어, 변경이 불가능함.

CasAuthenticationProvider의 역할

  • CasAuthenticationProvider(CAS)는 Spring Security에서 CAS (Central Authentication Service) 인증을 처리하는 데 사용되는 클래스로, CAS는 싱글 사인온(SSO)을 구현하기 위한 프로토콜 및 시스템.
  • CAS 서버로부터 인증 티켓을 검증하고, 사용자 세부 정보를 로드하며, 인증 토큰을 생성.
  • Spring Security 애플리케이션에서 중앙 집중식 인증을 지원. Spring Boot 앱은 인증을 위해 CAS를 사용

해결

코드

// CasAuthenticationProvider.java
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
	// ... other
	private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();

	/**
	 * Sets the UserDetailsChecker to be used for checking the status of retrieved user
	 * details. This allows customization of the UserDetailsChecker implementation.
	 * @param userDetailsChecker the UserDetailsChecker to be set
	 * @since 6.4
	 */
	public void setUserDetailsChecker(final UserDetailsChecker userDetailsChecker) {
		Assert.notNull(userDetailsChecker, "userDetailsChecker cannot be null");
		this.userDetailsChecker = userDetailsChecker;
	}
}

// TEST CODE

@Test
public void testSetUserDetailsChecker() throws AuthenticationException {
    CasAuthenticationProvider cap = new CasAuthenticationProvider();
    cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
    cap.setKey("qwerty");
    cap.setTicketValidator(new MockTicketValidator(true));
    cap.setServiceProperties(makeServiceProperties());
    cap.afterPropertiesSet();
    CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123");

    AtomicInteger checkCount = new AtomicInteger(0);
    UserDetailsChecker userDetailsChecker = new UserDetailsChecker() {
        @Override
        public void check(UserDetails user) {
            checkCount.incrementAndGet();
        }
    };
    cap.setUserDetailsChecker(userDetailsChecker);
    cap.authenticate(token);

    assertThat(checkCount.get()).isEqualTo(1);
}
  • CasAuthenticationProvider 클래스에 setUserDetailsChecker 메서드가 추가.
  • 이 메서드는 UserDetailsChecker를 설정하고, null 값을 허용하지 않음.
  • test code
    • 다른 테스트 코드에서 사용하고 있는 CasServiceTicketAuthenticationToken을 사용하여 test
    • AtomicInteger를 사용하여 check 메서드 호출 횟수를 추적.
      • authenticatie 메소드 내부에서 check 함수를 호출
    • UserDetailsChecker의 익명 클래스를 생성하여 check 메서드가 호출될 때마다 checkCount를 증가.
    • setUserDetailsChecker 메서드를 통해 커스텀 UserDetailsCheckerCasAuthenticationProvider에 설정.

느낀점

  • 인제님이 알려주신 내용인데, 해당 클래스는 getter가 모두 없었음.
    • 오픈소스에서는 getter를 열어 둘 경우, user가 접근이 가능.
    • 노출하면 getter를 다시 뺄 수 있기에, getter를 열어두지 않는 경우 있음.
  • test 코드를 만드는 것이 조금 까다로웠는데, 다른 사람들이 만든 test code를 참고하고, 구현되어 있는 클래스들을 사용하면 어렵지 않게 test 코드를 만들 수 있었음을 깨달았음.
  • 이처럼 누구나 손쉽게 해결 가능한 issue들이 있기에 오픈소스의 issue들에 조금 더 관심을 가져야겠다 생각이 들었음.

 

+ Recent posts